diff --git a/Cargo.lock b/Cargo.lock index 972f4e882..dcecf261e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3061,6 +3061,7 @@ checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", "miniz_oxide", + "zlib-rs", ] [[package]] @@ -8486,6 +8487,9 @@ dependencies = [ "twitch-irc", "url", "urlencoding", + "utoipa", + "utoipa-axum", + "utoipa-swagger-ui", "uuid", "zip 2.4.2", ] @@ -10119,6 +10123,62 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "utoipa" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fcc29c80c21c31608227e0912b2d7fddba57ad76b606890627ba8ee7964e993" +dependencies = [ + "indexmap 2.13.0", + "serde", + "serde_json", + "utoipa-gen", +] + +[[package]] +name = "utoipa-axum" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c25bae5bccc842449ec0c5ddc5cbb6a3a1eaeac4503895dc105a1138f8234a0" +dependencies = [ + "axum", + "paste", + "tower-layer", + "tower-service", + "utoipa", +] + +[[package]] +name = "utoipa-gen" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d79d08d92ab8af4c5e8a6da20c47ae3f61a0f1dabc1997cdf2d082b757ca08b" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn 2.0.114", + "uuid", +] + +[[package]] +name = "utoipa-swagger-ui" +version = "9.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d047458f1b5b65237c2f6dc6db136945667f40a7668627b3490b9513a3d43a55" +dependencies = [ + "axum", + "base64 0.22.1", + "mime_guess", + "regex", + "rust-embed", + "serde", + "serde_json", + "url", + "utoipa", + "zip 3.0.0", +] + [[package]] name = "uuid" version = "1.20.0" @@ -11253,6 +11313,26 @@ dependencies = [ "zstd", ] +[[package]] +name = "zip" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12598812502ed0105f607f941c386f43d441e00148fce9dec3ca5ffb0bde9308" +dependencies = [ + "arbitrary", + "crc32fast", + "flate2", + "indexmap 2.13.0", + "memchr", + "zopfli", +] + +[[package]] +name = "zlib-rs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" + [[package]] name = "zmij" version = "1.0.20" diff --git a/Cargo.toml b/Cargo.toml index f04c84646..3d6eaaade 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -126,6 +126,11 @@ rust-embed = { version = "8", features = ["mime-guess"] } mime_guess = "2" async-stream = "0.3" +# OpenAPI / utoipa +utoipa = { version = "5", features = ["axum_extras", "chrono", "uuid"] } +utoipa-axum = "0.2" +utoipa-swagger-ui = { version = "9", features = ["axum"] } + # Indoc for test fixtures indoc = "2" arrow-array = "57.3.0" @@ -175,6 +180,10 @@ parking_lot = "0.12" [target.'cfg(target_os = "macos")'.dependencies] security-framework = "3" +[[bin]] +name = "openapi-spec" +path = "src/bin/openapi_spec.rs" + [profile.release] lto = "thin" strip = true diff --git a/interface/bun.lock b/interface/bun.lock index 75d9b050a..4fc28204e 100644 --- a/interface/bun.lock +++ b/interface/bun.lock @@ -47,6 +47,7 @@ "graphology-layout-forceatlas2": "^0.10.1", "graphology-types": "^0.24.8", "ogl": "^1.0.11", + "openapi-fetch": "^0.17", "react": "^19.0.0", "react-dom": "^19.0.0", "react-hook-form": "^7.71.1", @@ -64,6 +65,7 @@ "@types/react-dom": "^19.0.0", "@vitejs/plugin-react": "^4.3.4", "autoprefixer": "^10.4.18", + "openapi-typescript": "^7", "postcss": "^8.4.36", "sass": "^1.72.0", "tailwindcss": "^3.4.1", @@ -460,6 +462,12 @@ "@react-sigma/core": ["@react-sigma/core@5.0.6", "", { "peerDependencies": { "graphology": "^0.26.0", "react": "^18.0.0 || ^19.0.0", "sigma": "^3.0.2" } }, "sha512-Xu2qXyvDZIhmvGC1n8d7Kcxm5Ntcz4HbPIM7CPDD2e4h3s/oxVpVPX7wtsNreJRRPj9mK+3oqB6SWXNI4mTqVg=="], + "@redocly/ajv": ["@redocly/ajv@8.11.2", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2", "uri-js-replace": "^1.0.1" } }, "sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg=="], + + "@redocly/config": ["@redocly/config@0.22.0", "", {}, "sha512-gAy93Ddo01Z3bHuVdPWfCwzgfaYgMdaZPcfL7JZ7hWJoK9V0lXDbigTWkhiPFAaLWzbOJ+kbUQG1+XwIm0KRGQ=="], + + "@redocly/openapi-core": ["@redocly/openapi-core@1.34.11", "", { "dependencies": { "@redocly/ajv": "8.11.2", "@redocly/config": "0.22.0", "colorette": "1.4.0", "https-proxy-agent": "7.0.6", "js-levenshtein": "1.1.6", "js-yaml": "4.1.1", "minimatch": "5.1.9", "pluralize": "8.0.0", "yaml-ast-parser": "0.0.43" } }, "sha512-V09ayfnb5GyysmvARbt+voFZAjGcf7hSYxOYxSkCc4fbH/DTfq5YWoec8cflvmHHqyIFbqvmGKmYFzqhr9zxDg=="], + "@reduxjs/toolkit": ["@reduxjs/toolkit@2.11.2", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@standard-schema/utils": "^0.3.0", "immer": "^11.0.0", "redux": "^5.0.1", "redux-thunk": "^3.1.0", "reselect": "^5.1.0" }, "peerDependencies": { "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" } }, ""], "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, ""], @@ -640,8 +648,12 @@ "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + "ahooks": ["ahooks@3.9.6", "", { "dependencies": { "@babel/runtime": "^7.21.0", "@types/js-cookie": "^3.0.6", "dayjs": "^1.9.1", "intersection-observer": "^0.12.0", "js-cookie": "^3.0.5", "lodash": "^4.17.21", "react-fast-compare": "^3.2.2", "resize-observer-polyfill": "^1.5.1", "screenfull": "^5.0.0", "tslib": "^2.4.1" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Mr7f05swd5SmKlR9SZo5U6M0LsL4ErweLzpdgXjA1JPmnZ78Vr6wzx0jUtvoxrcqGKYnX0Yjc02iEASVxHFPjQ=="], + "ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="], + "antd": ["antd@6.3.0", "", { "dependencies": { "@ant-design/colors": "^8.0.1", "@ant-design/cssinjs": "^2.1.0", "@ant-design/cssinjs-utils": "^2.1.1", "@ant-design/fast-color": "^3.0.1", "@ant-design/icons": "^6.1.0", "@ant-design/react-slick": "~2.0.0", "@babel/runtime": "^7.28.4", "@rc-component/cascader": "~1.14.0", "@rc-component/checkbox": "~2.0.0", "@rc-component/collapse": "~1.2.0", "@rc-component/color-picker": "~3.1.0", "@rc-component/dialog": "~1.8.4", "@rc-component/drawer": "~1.4.2", "@rc-component/dropdown": "~1.0.2", "@rc-component/form": "~1.6.2", "@rc-component/image": "~1.6.0", "@rc-component/input": "~1.1.2", "@rc-component/input-number": "~1.6.2", "@rc-component/mentions": "~1.6.0", "@rc-component/menu": "~1.2.0", "@rc-component/motion": "~1.1.6", "@rc-component/mutate-observer": "^2.0.1", "@rc-component/notification": "~1.2.0", "@rc-component/pagination": "~1.2.0", "@rc-component/picker": "~1.9.0", "@rc-component/progress": "~1.0.2", "@rc-component/qrcode": "~1.1.1", "@rc-component/rate": "~1.0.1", "@rc-component/resize-observer": "^1.1.1", "@rc-component/segmented": "~1.3.0", "@rc-component/select": "~1.6.5", "@rc-component/slider": "~1.0.1", "@rc-component/steps": "~1.2.2", "@rc-component/switch": "~1.0.3", "@rc-component/table": "~1.9.1", "@rc-component/tabs": "~1.7.0", "@rc-component/textarea": "~1.1.2", "@rc-component/tooltip": "~1.4.0", "@rc-component/tour": "~2.3.0", "@rc-component/tree": "~1.2.3", "@rc-component/tree-select": "~1.8.0", "@rc-component/trigger": "^3.9.0", "@rc-component/upload": "~1.1.0", "@rc-component/util": "^1.9.0", "clsx": "^2.1.1", "dayjs": "^1.11.11", "scroll-into-view-if-needed": "^3.1.0", "throttle-debounce": "^5.0.2" }, "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-bbHJcASrRHp02wTpr940KtUHlTT6tvmaD4OAjqgOJXNmTQ/+qBDdBVWY/yeDV41p/WbWjTLlaqRGVbL3UEVpNw=="], "antd-style": ["antd-style@4.1.0", "", { "dependencies": { "@ant-design/cssinjs": "^2.0.0", "@babel/runtime": "^7.24.1", "@emotion/cache": "^11.11.0", "@emotion/css": "^11.11.2", "@emotion/react": "^11.11.4", "@emotion/serialize": "^1.1.3", "@emotion/utils": "^1.2.1", "use-merge-value": "^1.2.0" }, "peerDependencies": { "antd": ">=6.0.0", "react": ">=18" } }, "sha512-vnPBGg0OVlSz90KRYZhxd89aZiOImTiesF+9MQqN8jsLGZUQTjbP04X9jTdEfsztKUuMbBWg/RmB/wHTakbtMQ=="], @@ -652,6 +664,8 @@ "arg": ["arg@5.0.2", "", {}, ""], + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + "aria-hidden": ["aria-hidden@1.2.6", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA=="], "assign-symbols": ["assign-symbols@1.0.0", "", {}, "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw=="], @@ -666,10 +680,14 @@ "bail": ["bail@2.0.2", "", {}, ""], + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "baseline-browser-mapping": ["baseline-browser-mapping@2.9.19", "", { "bin": "dist/cli.js" }, ""], "binary-extensions": ["binary-extensions@2.3.0", "", {}, ""], + "brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, ""], "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": "cli.js" }, ""], @@ -682,6 +700,8 @@ "ccount": ["ccount@2.0.1", "", {}, ""], + "change-case": ["change-case@5.4.4", "", {}, "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w=="], + "character-entities": ["character-entities@2.0.2", "", {}, ""], "character-entities-html4": ["character-entities-html4@2.1.0", "", {}, ""], @@ -712,6 +732,8 @@ "colord": ["colord@2.9.3", "", {}, "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw=="], + "colorette": ["colorette@1.4.0", "", {}, "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g=="], + "comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, ""], "commander": ["commander@4.1.1", "", {}, ""], @@ -964,6 +986,8 @@ "html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="], + "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], "immer": ["immer@10.2.0", "", {}, ""], @@ -972,6 +996,8 @@ "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], + "index-to-position": ["index-to-position@1.2.0", "", {}, "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw=="], + "inline-style-parser": ["inline-style-parser@0.2.7", "", {}, ""], "internmap": ["internmap@2.0.3", "", {}, ""], @@ -1014,12 +1040,18 @@ "js-cookie": ["js-cookie@3.0.5", "", {}, "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw=="], + "js-levenshtein": ["js-levenshtein@1.1.6", "", {}, "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g=="], + "js-tokens": ["js-tokens@4.0.0", "", {}, ""], + "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + "jsesc": ["jsesc@3.1.0", "", { "bin": "bin/jsesc" }, ""], "json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="], + "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + "json2mq": ["json2mq@0.2.0", "", { "dependencies": { "string-convert": "^0.2.0" } }, "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA=="], "json5": ["json5@2.2.3", "", { "bin": "lib/cli.js" }, ""], @@ -1184,6 +1216,8 @@ "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, ""], + "minimatch": ["minimatch@5.1.9", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw=="], + "mixin-deep": ["mixin-deep@1.3.2", "", { "dependencies": { "for-in": "^1.0.2", "is-extendable": "^1.0.1" } }, "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA=="], "mlly": ["mlly@1.8.0", "", { "dependencies": { "acorn": "^8.15.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.1" } }, "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g=="], @@ -1220,13 +1254,19 @@ "oniguruma-to-es": ["oniguruma-to-es@4.3.4", "", { "dependencies": { "oniguruma-parser": "^0.12.1", "regex": "^6.0.1", "regex-recursion": "^6.0.2" } }, "sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA=="], + "openapi-fetch": ["openapi-fetch@0.17.0", "", { "dependencies": { "openapi-typescript-helpers": "^0.1.0" } }, "sha512-PsbZR1wAPcG91eEthKhN+Zn92FMHxv+/faECIwjXdxfTODGSGegYv0sc1Olz+HYPvKOuoXfp+0pA2XVt2cI0Ig=="], + + "openapi-typescript": ["openapi-typescript@7.13.0", "", { "dependencies": { "@redocly/openapi-core": "^1.34.6", "ansi-colors": "^4.1.3", "change-case": "^5.4.4", "parse-json": "^8.3.0", "supports-color": "^10.2.2", "yargs-parser": "^21.1.1" }, "peerDependencies": { "typescript": "^5.x" }, "bin": { "openapi-typescript": "bin/cli.js" } }, "sha512-EFP392gcqXS7ntPvbhBzbF8TyBA+baIYEm791Hy5YkjDYKTnk/Tn5OQeKm5BIZvJihpp8Zzr4hzx0Irde1LNGQ=="], + + "openapi-typescript-helpers": ["openapi-typescript-helpers@0.1.0", "", {}, "sha512-OKTGPthhivLw/fHz6c3OPtg72vi86qaMlqbJuVJ23qOvQ+53uw1n7HdmkJFibloF7QEjDrDkzJiOJuockM/ljw=="], + "package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="], "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], "parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, ""], - "parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="], + "parse-json": ["parse-json@8.3.0", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "index-to-position": "^1.1.0", "type-fest": "^4.39.1" } }, "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ=="], "parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], @@ -1248,6 +1288,8 @@ "pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], + "pluralize": ["pluralize@8.0.0", "", {}, "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA=="], + "points-on-curve": ["points-on-curve@0.2.0", "", {}, "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A=="], "points-on-path": ["points-on-path@0.2.1", "", { "dependencies": { "path-data-parser": "0.1.0", "points-on-curve": "0.2.0" } }, "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g=="], @@ -1392,6 +1434,8 @@ "remend": ["remend@1.2.0", "", {}, "sha512-NbKrdWweTRuByPYErzQCNpNtsR9M1QQ0hK2UzmnmlSaEqHnkQ5Korlyi8KpdbOJ0rImJfRy4EAY0uDxYnL9Plw=="], + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + "reselect": ["reselect@5.1.1", "", {}, ""], "resize-observer-polyfill": ["resize-observer-polyfill@1.5.1", "", {}, "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="], @@ -1466,6 +1510,8 @@ "sucrase": ["sucrase@3.35.1", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, ""], + "supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="], + "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, ""], "swr": ["swr@2.4.0", "", { "dependencies": { "dequal": "^2.0.3", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-sUlC20T8EOt1pHmDiqueUWMmRRX03W7w5YxovWX7VR2KHEPCTMly85x05vpkP5i6Bu4h44ePSMD9Tc+G2MItFw=="], @@ -1506,6 +1552,8 @@ "tslib": ["tslib@2.8.1", "", {}, ""], + "type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, ""], "ufo": ["ufo@1.6.3", "", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="], @@ -1530,6 +1578,8 @@ "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": "cli.js" }, ""], + "uri-js-replace": ["uri-js-replace@1.0.1", "", {}, "sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g=="], + "url-join": ["url-join@5.0.0", "", {}, "sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA=="], "use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="], @@ -1578,6 +1628,10 @@ "yaml": ["yaml@1.10.2", "", {}, "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="], + "yaml-ast-parser": ["yaml-ast-parser@0.0.43", "", {}, "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A=="], + + "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], "zustand": ["zustand@4.5.7", "", { "dependencies": { "use-sync-external-store": "^1.2.2" }, "peerDependencies": { "@types/react": ">=16.8", "immer": ">=9.0.6", "react": ">=16.8" }, "optionalPeers": ["@types/react", "immer", "react"] }, "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw=="], @@ -1628,6 +1682,8 @@ "chevrotain/lodash-es": ["lodash-es@4.17.21", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="], + "cosmiconfig/parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="], + "cytoscape-fcose/cose-base": ["cose-base@2.2.0", "", { "dependencies": { "layout-base": "^2.0.0" } }, "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g=="], "d3-dsv/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="], diff --git a/interface/package.json b/interface/package.json index 2f622977a..80cb3661f 100644 --- a/interface/package.json +++ b/interface/package.json @@ -51,6 +51,7 @@ "graphology-layout-forceatlas2": "^0.10.1", "graphology-types": "^0.24.8", "ogl": "^1.0.11", + "openapi-fetch": "^0.17", "react": "^19.0.0", "react-dom": "^19.0.0", "react-hook-form": "^7.71.1", @@ -68,6 +69,7 @@ "@types/react-dom": "^19.0.0", "@vitejs/plugin-react": "^4.3.4", "autoprefixer": "^10.4.18", + "openapi-typescript": "^7", "postcss": "^8.4.36", "sass": "^1.72.0", "tailwindcss": "^3.4.1", diff --git a/interface/src/api/client-typed.ts b/interface/src/api/client-typed.ts new file mode 100644 index 000000000..0f4427559 --- /dev/null +++ b/interface/src/api/client-typed.ts @@ -0,0 +1,24 @@ +import createClient from "openapi-fetch"; +import type { paths } from "./schema"; + +let baseUrl = ""; + +export function setServerUrl(url: string) { + baseUrl = url; +} + +function getClient() { + return createClient({ + baseUrl: baseUrl ? `${baseUrl}/api` : "/api", + headers: getAuthHeaders(), + }); +} + +function getAuthHeaders(): Record { + const token = localStorage.getItem("spacebot_auth_token"); + return token ? { Authorization: `Bearer ${token}` } : {}; +} + +// Re-export the typed client for direct use +export { getClient }; +export type { paths }; diff --git a/interface/src/api/client.ts b/interface/src/api/client.ts index 94fc6a555..28ea9c64c 100644 --- a/interface/src/api/client.ts +++ b/interface/src/api/client.ts @@ -18,26 +18,85 @@ function getApiBase(): string { return BASE_PATH + "/api"; } -export interface StatusResponse { - status: string; - version: string; - pid: number; - uptime_seconds: number; -} - -export interface ChannelInfo { - agent_id: string; - id: string; - platform: string; - display_name: string | null; - is_active: boolean; - last_activity_at: string; - created_at: string; -} - -export interface ChannelsResponse { - channels: ChannelInfo[]; -} +import type * as Types from "./types"; + +// Re-export commonly used types from schema for backward compatibility +// Only re-export types that don't have local definitions with extra fields +export type { + // System + StatusResponse, + InstanceOverviewResponse, + // Channels + ChannelResponse, + ChannelsResponse, + MessagesResponse, + TimelineItem, + // Workers + WorkerListItem, + WorkerListResponse, + WorkerDetailResponse, + TranscriptStep, + // Agents + AgentInfo, + AgentsResponse, + AgentSummary, + AgentOverviewResponse, + AgentProfile, + AgentProfileResponse, + CronJobInfo, + // Memory (schema types only) + Memory, + Association, + RelationType, + MemoryGraphResponse, + MemoryGraphNeighborsResponse, + // Cortex chat (schema types) + CortexChatMessage, + CortexChatThread, + CortexChatToolCall, + CortexChatMessagesResponse, + CortexChatThreadsResponse, + // Config (schema types only) + GlobalSettingsResponse, + GlobalSettingsUpdateResponse, + RawConfigResponse, + RawConfigUpdateResponse, + // Providers + ProvidersResponse, + ProviderUpdateResponse, + ProviderModelTestResponse, + OpenAiOAuthBrowserStartResponse, + OpenAiOAuthBrowserStatusResponse, + ModelInfo, + ModelsResponse, + // Ingest + IngestFileInfo, + IngestFilesResponse, + IngestUploadResponse, + IngestDeleteResponse, + // Messaging + PlatformStatus, + AdapterInstanceStatus, + MessagingStatusResponse, + CreateMessagingInstanceRequest, + MessagingInstanceActionResponse, +} from "./types"; + +// Import and re-export Topology types from schema +import type { + TopologyAgent, + TopologyLink, + TopologyGroup, + TopologyHuman, + TopologyResponse, +} from "./types"; + +export type { TopologyAgent, TopologyLink, TopologyGroup, TopologyHuman, TopologyResponse }; + +// Aliases for backward compatibility +export type ChannelInfo = Types.ChannelResponse; +export type WorkerRunInfo = Types.WorkerListItem; +export type AssociationItem = Types.Association; export type ProcessType = "channel" | "branch" | "worker"; @@ -142,6 +201,15 @@ export interface ToolCompletedEvent { result: string; } +// -- Agent link events -- + +export interface AgentMessageEvent { + from_agent_id: string; + to_agent_id: string; + link_id: string; + channel_id: string; +} + // -- OpenCode live transcript part types -- export type OpenCodeToolState = @@ -175,7 +243,7 @@ export interface CortexChatMessageEvent { agent_id: string; thread_id: string; content: string; - tool_calls?: CortexChatToolCall[]; + tool_calls?: Types.CortexChatToolCall[]; } export type ApiEvent = @@ -195,13 +263,7 @@ export type ApiEvent = | WorkerTextEvent | CortexChatMessageEvent; -async function fetchJson(path: string): Promise { - const response = await fetch(`${getApiBase()}${path}`); - if (!response.ok) { - throw new Error(`API error: ${response.status}`); - } - return response.json(); -} +// -- Timeline types (discriminated union parts) -- export interface TimelineMessage { type: "message"; @@ -232,13 +294,19 @@ export interface TimelineWorkerRun { completed_at: string | null; } -export type TimelineItem = TimelineMessage | TimelineBranchRun | TimelineWorkerRun; +// Note: TimelineItem is re-exported from types.ts as a union type -export interface MessagesResponse { - items: TimelineItem[]; - has_more: boolean; +async function fetchJson(path: string): Promise { + const response = await fetch(`${getApiBase()}${path}`); + if (!response.ok) { + throw new Error(`API error: ${response.status}`); + } + return response.json(); } +/** channel_id -> StatusBlockSnapshot */ +export type ChannelStatusResponse = Record; + export interface WorkerStatusInfo { id: string; task: string; @@ -269,9 +337,6 @@ export interface StatusBlockSnapshot { completed_items: CompletedItemInfo[]; } -/** channel_id -> StatusBlockSnapshot */ -export type ChannelStatusResponse = Record; - export interface PromptInspectResponse { channel_id: string; system_prompt: string; @@ -311,153 +376,9 @@ export interface PromptCaptureResponse { capture_enabled: boolean; } -// --- Workers API types --- - -export type ActionContent = - | { type: "text"; text: string } - | { type: "tool_call"; id: string; name: string; args: string }; - -export type TranscriptStep = - | { type: "action"; content: ActionContent[] } - | { type: "tool_result"; call_id: string; name: string; text: string }; - -export interface WorkerRunInfo { - id: string; - task: string; - status: string; - worker_type: string; - channel_id: string | null; - channel_name: string | null; - started_at: string; - completed_at: string | null; - has_transcript: boolean; - live_status: string | null; - tool_calls: number; - opencode_port: number | null; - opencode_session_id: string | null; - directory: string | null; - interactive: boolean; - project_id: string | null; - project_name: string | null; -} - -export interface WorkerDetailResponse { - id: string; - task: string; - result: string | null; - status: string; - worker_type: string; - channel_id: string | null; - channel_name: string | null; - started_at: string; - completed_at: string | null; - transcript: TranscriptStep[] | null; - tool_calls: number; - opencode_session_id: string | null; - opencode_port: number | null; - interactive: boolean; - directory: string | null; -} - -export interface WorkerListResponse { - workers: WorkerRunInfo[]; - total: number; -} - -export interface AgentInfo { - id: string; - display_name?: string; - role?: string; - gradient_start?: string; - gradient_end?: string; - workspace: string; - context_window: number; - max_turns: number; - max_concurrent_branches: number; - max_concurrent_workers: number; -} - -export interface AgentsResponse { - agents: AgentInfo[]; -} - -export interface CronJobInfo { - id: string; - prompt: string; - cron_expr: string | null; - interval_secs: number; - delivery_target: string; - enabled: boolean; - run_once: boolean; - active_hours: [number, number] | null; -} - -export interface AgentOverviewResponse { - memory_counts: Record; - memory_total: number; - channel_count: number; - cron_jobs: CronJobInfo[]; - last_bulletin_at: string | null; - recent_cortex_events: CortexEvent[]; - memory_daily: { date: string; count: number }[]; - activity_daily: { date: string; branches: number; workers: number }[]; - activity_heatmap: { day: number; hour: number; count: number }[]; - latest_bulletin: string | null; -} - -export interface AgentProfile { - agent_id: string; - display_name: string | null; - status: string | null; - bio: string | null; - avatar_seed: string | null; - generated_at: string; - updated_at: string; -} - -export interface AgentProfileResponse { - profile: AgentProfile | null; -} - -export interface AgentSummary { - id: string; - channel_count: number; - memory_total: number; - cron_job_count: number; - activity_sparkline: number[]; - last_activity_at: string | null; - last_bulletin_at: string | null; - profile: AgentProfile | null; -} - -export interface InstanceOverviewResponse { - version: string; - uptime_seconds: number; - pid: number; - agents: AgentSummary[]; -} - -export type Deployment = "docker" | "hosted" | "native"; - -export interface UpdateStatus { - current_version: string; - latest_version: string | null; - update_available: boolean; - release_url: string | null; - release_notes: string | null; - deployment: Deployment; - can_apply: boolean; - cannot_apply_reason: string | null; - docker_image: string | null; - checked_at: string | null; - error: string | null; -} - -export interface UpdateApplyResponse { - status: "updating" | "error"; - error?: string; -} +// --- Memory helper types (extended beyond schema) --- +// Extended MemoryType with additional values not yet in schema export type MemoryType = | "fact" | "preference" @@ -475,6 +396,7 @@ export const MEMORY_TYPES: MemoryType[] = [ export type MemorySort = "recent" | "importance" | "most_accessed"; +// Extended MemoryItem with forgotten field (not yet in schema) export interface MemoryItem { id: string; content: string; @@ -504,34 +426,6 @@ export interface MemoriesSearchResponse { results: MemorySearchResultItem[]; } -export type RelationType = - | "related_to" - | "updates" - | "contradicts" - | "caused_by" - | "result_of" - | "part_of"; - -export interface AssociationItem { - id: string; - source_id: string; - target_id: string; - relation_type: RelationType; - weight: number; - created_at: string; -} - -export interface MemoryGraphResponse { - nodes: MemoryItem[]; - edges: AssociationItem[]; - total: number; -} - -export interface MemoryGraphNeighborsResponse { - nodes: MemoryItem[]; - edges: AssociationItem[]; -} - export interface MemoryGraphParams { limit?: number; offset?: number; @@ -556,6 +450,8 @@ export interface MemoriesSearchParams { memory_type?: MemoryType; } +// --- Cortex event types --- + export type CortexEventType = | "bulletin_generated" | "bulletin_failed" @@ -598,48 +494,13 @@ export interface CortexEventsParams { event_type?: CortexEventType; } -// -- Cortex Chat -- - -export interface CortexChatToolCall { - id: string; - tool: string; - args: string; - result: string | null; - status: "running" | "completed" | "error"; -} - -export interface CortexChatMessage { - id: string; - thread_id: string; - role: "user" | "assistant"; - content: string; - channel_context: string | null; - created_at: string; - tool_calls?: CortexChatToolCall[]; -} - -export interface CortexChatMessagesResponse { - messages: CortexChatMessage[]; - thread_id: string; -} - -export interface CortexChatThread { - thread_id: string; - preview: string; - message_count: number; - first_message_at: string; - last_message_at: string; -} - -export interface CortexChatThreadsResponse { - threads: CortexChatThread[]; -} +// -- Cortex Chat SSE types (not in schema) -- export type CortexChatSSEEvent = | { type: "thinking" } | { type: "tool_started"; tool: string; call_id: string; args: string } | { type: "tool_completed"; tool: string; call_id: string; args: string; result: string; result_preview: string } - | { type: "done"; full_text: string; tool_calls: CortexChatToolCall[] } + | { type: "done"; full_text: string; tool_calls: Types.CortexChatToolCall[] } | { type: "error"; message: string }; // -- Factory Presets -- @@ -662,20 +523,7 @@ export interface PresetsResponse { presets: PresetMeta[]; } -export interface IdentityFiles { - soul: string | null; - identity: string | null; - role: string | null; -} - -export interface IdentityUpdateRequest { - agent_id: string; - soul?: string | null; - identity?: string | null; - role?: string | null; -} - -// -- Agent Config Types -- +// -- Config types with frontend-specific extensions -- export interface RoutingSection { channel: string; @@ -930,103 +778,62 @@ export interface CronExecutionsParams { limit?: number; } -export interface ProviderStatus { - anthropic: boolean; - openai: boolean; - openai_chatgpt: boolean; - openrouter: boolean; - kilo: boolean; - zhipu: boolean; - groq: boolean; - together: boolean; - fireworks: boolean; - deepseek: boolean; - xai: boolean; - mistral: boolean; - gemini: boolean; - ollama: boolean; - opencode_zen: boolean; - opencode_go: boolean; - nvidia: boolean; - minimax: boolean; - minimax_cn: boolean; - moonshot: boolean; - zai_coding_plan: boolean; - github_copilot: boolean; -} - -export interface ProvidersResponse { - providers: ProviderStatus; - has_any: boolean; -} - -export interface ProviderActionResponse { - success: boolean; - message: string; -} +// -- Update Types -- -export interface ProviderModelTestResponse { - success: boolean; - message: string; - provider: string; - model: string; - sample: string | null; -} - -export interface OpenAiOAuthBrowserStartResponse { - success: boolean; - message: string; - user_code: string | null; - verification_url: string | null; - state: string | null; -} - -export interface OpenAiOAuthBrowserStatusResponse { - found: boolean; - done: boolean; - success: boolean; - message: string | null; -} - -// -- Model Types -- +export type Deployment = "docker" | "hosted" | "native"; -export interface ModelInfo { - id: string; - name: string; - provider: string; - context_window: number | null; - tool_call: boolean; - reasoning: boolean; - input_audio: boolean; +export interface UpdateStatus { + current_version: string; + latest_version: string | null; + update_available: boolean; + release_url: string | null; + release_notes: string | null; + deployment: Deployment; + can_apply: boolean; + cannot_apply_reason: string | null; + docker_image: string | null; + checked_at: string | null; + error: string | null; } -export interface ModelsResponse { - models: ModelInfo[]; +export interface UpdateApplyResponse { + status: "updating" | "error"; + error?: string; } -// -- Ingest Types -- +// -- Global Settings Types -- -export interface IngestFileInfo { - content_hash: string; - filename: string; - file_size: number; - total_chunks: number; - chunks_completed: number; - status: "queued" | "processing" | "completed" | "failed"; - started_at: string; - completed_at: string | null; +export interface OpenCodePermissions { + edit: string; + bash: string; + webfetch: string; } -export interface IngestFilesResponse { - files: IngestFileInfo[]; +export interface OpenCodeSettings { + enabled: boolean; + path: string; + max_servers: number; + server_startup_timeout_secs: number; + max_restart_retries: number; + permissions: OpenCodePermissions; } -export interface IngestUploadResponse { - uploaded: string[]; +export interface OpenCodeSettingsUpdate { + enabled?: boolean; + path?: string; + max_servers?: number; + server_startup_timeout_secs?: number; + max_restart_retries?: number; + permissions?: Partial; } -export interface IngestDeleteResponse { - success: boolean; +export interface GlobalSettingsUpdate { + brave_search_key?: string | null; + api_enabled?: boolean; + api_port?: number; + api_bind?: string; + worker_log_mode?: string; + opencode?: OpenCodeSettingsUpdate; } // -- Skills Types -- @@ -1103,12 +910,6 @@ export interface UploadSkillResponse { installed: string[]; } -export interface RegistrySkillContentResponse { - source: string; - skill_id: string; - content: string | null; -} - // -- Task Types -- export type TaskStatus = "pending_approval" | "backlog" | "ready" | "in_progress" | "done"; @@ -1181,76 +982,6 @@ export interface UpdateTaskRequest { // -- Messaging / Bindings Types -- -export interface PlatformStatus { - configured: boolean; - enabled: boolean; -} - -export interface AdapterInstanceStatus { - platform: string; - name: string | null; - runtime_key: string; - configured: boolean; - enabled: boolean; - binding_count: number; -} - -export interface MessagingStatusResponse { - discord: PlatformStatus; - slack: PlatformStatus; - telegram: PlatformStatus; - webhook: PlatformStatus; - twitch: PlatformStatus; - email: PlatformStatus; - mattermost: PlatformStatus; - signal: PlatformStatus; - instances: AdapterInstanceStatus[]; -} - -export interface CreateMessagingInstanceRequest { - platform: string; - name?: string; - enabled?: boolean; - credentials: { - discord_token?: string; - slack_bot_token?: string; - slack_app_token?: string; - telegram_token?: string; - twitch_username?: string; - twitch_oauth_token?: string; - twitch_client_id?: string; - twitch_client_secret?: string; - twitch_refresh_token?: string; - email_imap_host?: string; - email_imap_port?: number; - email_imap_username?: string; - email_imap_password?: string; - email_smtp_host?: string; - email_smtp_port?: number; - email_smtp_username?: string; - email_smtp_password?: string; - email_from_address?: string; - webhook_port?: number; - webhook_bind?: string; - webhook_auth_token?: string; - mattermost_base_url?: string; - mattermost_token?: string; - signal_http_url?: string; - signal_account?: string; - signal_dm_allowed_users?: string; - }; -} - -export interface DeleteMessagingInstanceRequest { - platform: string; - name?: string; -} - -export interface MessagingInstanceActionResponse { - success: boolean; - message: string; -} - export interface BindingInfo { agent_id: string; channel: string; @@ -1343,66 +1074,7 @@ export interface DeleteBindingResponse { message: string; } -// -- Global Settings Types -- - -export interface OpenCodePermissions { - edit: string; - bash: string; - webfetch: string; -} - -export interface OpenCodeSettings { - enabled: boolean; - path: string; - max_servers: number; - server_startup_timeout_secs: number; - max_restart_retries: number; - permissions: OpenCodePermissions; -} - -export interface OpenCodeSettingsUpdate { - enabled?: boolean; - path?: string; - max_servers?: number; - server_startup_timeout_secs?: number; - max_restart_retries?: number; - permissions?: Partial; -} - -export interface GlobalSettingsResponse { - brave_search_key: string | null; - api_enabled: boolean; - api_port: number; - api_bind: string; - worker_log_mode: string; - opencode: OpenCodeSettings; -} - -export interface GlobalSettingsUpdate { - brave_search_key?: string | null; - api_enabled?: boolean; - api_port?: number; - api_bind?: string; - worker_log_mode?: string; - opencode?: OpenCodeSettingsUpdate; -} - -export interface GlobalSettingsUpdateResponse { - success: boolean; - message: string; - requires_restart: boolean; -} - -export interface RawConfigResponse { - content: string; -} - -export interface RawConfigUpdateResponse { - success: boolean; - message: string; -} - -// -- Agent Links & Topology -- +// -- Links & Topology Types -- export type LinkDirection = "one_way" | "two_way"; export type LinkKind = "hierarchical" | "peer"; @@ -1418,45 +1090,6 @@ export interface LinksResponse { links: AgentLinkResponse[]; } -export interface TopologyAgent { - id: string; - name: string; - display_name?: string; - role?: string; -} - -export interface TopologyLink { - from: string; - to: string; - direction: string; - kind: string; -} - -export interface TopologyGroup { - name: string; - agent_ids: string[]; - color?: string; -} - -export interface TopologyHuman { - id: string; - display_name?: string; - role?: string; - bio?: string; - description?: string; - discord_id?: string; - telegram_id?: string; - slack_id?: string; - email?: string; -} - -export interface TopologyResponse { - agents: TopologyAgent[]; - humans: TopologyHuman[]; - links: TopologyLink[]; - groups: TopologyGroup[]; -} - export interface CreateHumanRequest { id: string; display_name?: string; @@ -1504,14 +1137,7 @@ export interface UpdateLinkRequest { kind?: LinkKind; } -export interface AgentMessageEvent { - from_agent_id: string; - to_agent_id: string; - link_id: string; - channel_id: string; -} - -// ── Projects ───────────────────────────────────────────────────────────── +// -- Projects Types -- export type ProjectStatus = "active" | "archived"; @@ -1571,11 +1197,6 @@ export interface ProjectWithRelations extends Project { worktrees: ProjectWorktreeWithRepo[]; } -export interface ProjectDetailResponse { - /** The flattened project + repos + worktrees (serde #[flatten]) */ - [key: string]: unknown; -} - export interface ProjectActionResponse { success: boolean; message: string; @@ -1626,7 +1247,7 @@ export interface CreateWorktreeRequest { start_point?: string; } -// ── Secrets ────────────────────────────────────────────────────────────── +// -- Secrets Types -- export type SecretCategory = "system" | "tool"; export type StoreState = "unencrypted" | "locked" | "unlocked"; @@ -1687,13 +1308,13 @@ export interface MigrateResponse { } export const api = { - status: () => fetchJson("/status"), - overview: () => fetchJson("/overview"), - agents: () => fetchJson("/agents"), + status: () => fetchJson("/status"), + overview: () => fetchJson("/agents/instance"), + agents: () => fetchJson("/agents"), factoryPresets: () => fetchJson("/factory/presets"), agentOverview: (agentId: string) => - fetchJson(`/agents/overview?agent_id=${encodeURIComponent(agentId)}`), - channels: () => fetchJson("/channels"), + fetchJson(`/agents/overview?agent_id=${encodeURIComponent(agentId)}`), + channels: () => fetchJson("/channels"), deleteChannel: async (agentId: string, channelId: string) => { const params = new URLSearchParams({ agent_id: agentId, channel_id: channelId }); const response = await fetch(`${getApiBase()}/channels?${params}`, { method: "DELETE" }); @@ -1703,7 +1324,7 @@ export const api = { channelMessages: (channelId: string, limit = 20, before?: string) => { const params = new URLSearchParams({ channel_id: channelId, limit: String(limit) }); if (before) params.set("before", before); - return fetchJson(`/channels/messages?${params}`); + return fetchJson(`/channels/messages?${params}`); }, channelStatus: () => fetchJson("/channels/status"), inspectPrompt: (channelId: string) => @@ -1730,11 +1351,11 @@ export const api = { if (params.limit) search.set("limit", String(params.limit)); if (params.offset) search.set("offset", String(params.offset)); if (params.status) search.set("status", params.status); - return fetchJson(`/agents/workers?${search}`); + return fetchJson(`/agents/workers?${search}`); }, workerDetail: (agentId: string, workerId: string) => - fetchJson(`/agents/workers/detail?agent_id=${encodeURIComponent(agentId)}&worker_id=${encodeURIComponent(workerId)}`), - agentMemories: (agentId: string, params: MemoriesListParams = {}) => { + fetchJson(`/agents/workers/detail?agent_id=${encodeURIComponent(agentId)}&worker_id=${encodeURIComponent(workerId)}`), + agentMemories: (agentId: string, params: MemoryGraphParams = {}) => { const search = new URLSearchParams({ agent_id: agentId }); if (params.limit) search.set("limit", String(params.limit)); if (params.offset) search.set("offset", String(params.offset)); @@ -1754,13 +1375,13 @@ export const api = { if (params.offset) search.set("offset", String(params.offset)); if (params.memory_type) search.set("memory_type", params.memory_type); if (params.sort) search.set("sort", params.sort); - return fetchJson(`/agents/memories/graph?${search}`); + return fetchJson(`/agents/memories/graph?${search}`); }, memoryGraphNeighbors: (agentId: string, memoryId: string, params: MemoryGraphNeighborsParams = {}) => { const search = new URLSearchParams({ agent_id: agentId, memory_id: memoryId }); if (params.depth) search.set("depth", String(params.depth)); if (params.exclude?.length) search.set("exclude", params.exclude.join(",")); - return fetchJson(`/agents/memories/graph/neighbors?${search}`); + return fetchJson(`/agents/memories/graph/neighbors?${search}`); }, cortexEvents: (agentId: string, params: CortexEventsParams = {}) => { const search = new URLSearchParams({ agent_id: agentId }); @@ -1772,7 +1393,7 @@ export const api = { cortexChatMessages: (agentId: string, threadId?: string, limit = 50) => { const search = new URLSearchParams({ agent_id: agentId, limit: String(limit) }); if (threadId) search.set("thread_id", threadId); - return fetchJson(`/cortex-chat/messages?${search}`); + return fetchJson(`/cortex-chat/messages?${search}`); }, cortexChatSend: (agentId: string, threadId: string, message: string, channelId?: string) => fetch(`${getApiBase()}/cortex-chat/send`, { @@ -1786,7 +1407,7 @@ export const api = { }), }), cortexChatThreads: (agentId: string) => - fetchJson( + fetchJson( `/cortex-chat/threads?agent_id=${encodeURIComponent(agentId)}`, ), cortexChatDeleteThread: async (agentId: string, threadId: string) => { @@ -1798,10 +1419,10 @@ export const api = { if (!response.ok) throw new Error(`HTTP ${response.status}`); }, agentProfile: (agentId: string) => - fetchJson(`/agents/profile?agent_id=${encodeURIComponent(agentId)}`), + fetchJson(`/agents/profile?agent_id=${encodeURIComponent(agentId)}`), agentIdentity: (agentId: string) => - fetchJson(`/agents/identity?agent_id=${encodeURIComponent(agentId)}`), - updateIdentity: async (request: IdentityUpdateRequest) => { + fetchJson<{ soul: string | null; identity: string | null; role: string | null }>(`/agents/identity?agent_id=${encodeURIComponent(agentId)}`), + updateIdentity: async (request: { agent_id: string; soul?: string | null; identity?: string | null; role?: string | null }) => { const response = await fetch(`${getApiBase()}/agents/identity`, { method: "PUT", headers: { "Content-Type": "application/json" }, @@ -1810,7 +1431,7 @@ export const api = { if (!response.ok) { throw new Error(`API error: ${response.status}`); } - return response.json() as Promise; + return response.json() as Promise<{ soul: string | null; identity: string | null; role: string | null }>; }, createAgent: async (agentId: string, displayName?: string, role?: string) => { const response = await fetch(`${getApiBase()}/agents`, { @@ -1961,7 +1582,7 @@ export const api = { }, // Provider management - providers: () => fetchJson("/providers"), + providers: () => fetchJson("/providers"), updateProvider: async (provider: string, apiKey: string, model: string) => { const response = await fetch(`${getApiBase()}/providers`, { method: "PUT", @@ -1971,7 +1592,7 @@ export const api = { if (!response.ok) { throw new Error(`API error: ${response.status}`); } - return response.json() as Promise; + return response.json() as Promise; }, testProviderModel: async (provider: string, apiKey: string, model: string) => { const response = await fetch(`${getApiBase()}/providers/test`, { @@ -1982,7 +1603,7 @@ export const api = { if (!response.ok) { throw new Error(`API error: ${response.status}`); } - return response.json() as Promise; + return response.json() as Promise; }, startOpenAiOAuthBrowser: async (params: {model: string}) => { const response = await fetch(`${getApiBase()}/providers/openai/oauth/browser/start`, { @@ -1995,7 +1616,7 @@ export const api = { if (!response.ok) { throw new Error(`API error: ${response.status}`); } - return response.json() as Promise; + return response.json() as Promise; }, openAiOAuthBrowserStatus: async (state: string) => { const response = await fetch( @@ -2004,7 +1625,7 @@ export const api = { if (!response.ok) { throw new Error(`API error: ${response.status}`); } - return response.json() as Promise; + return response.json() as Promise; }, removeProvider: async (provider: string) => { const response = await fetch(`${getApiBase()}/providers/${encodeURIComponent(provider)}`, { @@ -2013,7 +1634,7 @@ export const api = { if (!response.ok) { throw new Error(`API error: ${response.status}`); } - return response.json() as Promise; + return response.json() as Promise; }, // Model listing @@ -2022,7 +1643,7 @@ export const api = { if (provider) params.set("provider", provider); if (capability) params.set("capability", capability); const query = params.toString() ? `?${params.toString()}` : ""; - return fetchJson(`/models${query}`); + return fetchJson(`/models${query}`); }, refreshModels: async () => { const response = await fetch(`${getApiBase()}/models/refresh`, { @@ -2031,12 +1652,12 @@ export const api = { if (!response.ok) { throw new Error(`API error: ${response.status}`); } - return response.json() as Promise; + return response.json() as Promise; }, // Ingest API ingestFiles: (agentId: string) => - fetchJson(`/agents/ingest/files?agent_id=${encodeURIComponent(agentId)}`), + fetchJson(`/agents/ingest/files?agent_id=${encodeURIComponent(agentId)}`), uploadIngestFiles: async (agentId: string, files: File[]) => { const formData = new FormData(); @@ -2050,7 +1671,7 @@ export const api = { if (!response.ok) { throw new Error(`API error: ${response.status}`); } - return response.json() as Promise; + return response.json() as Promise; }, deleteIngestFile: async (agentId: string, contentHash: string) => { @@ -2061,11 +1682,11 @@ export const api = { if (!response.ok) { throw new Error(`API error: ${response.status}`); } - return response.json() as Promise; + return response.json() as Promise; }, // Messaging / Bindings API - messagingStatus: () => fetchJson("/messaging/status"), + messagingStatus: () => fetchJson("/messaging/status"), bindings: (agentId?: string) => { const params = agentId @@ -2138,7 +1759,7 @@ export const api = { return response.json() as Promise<{ success: boolean; message: string }>; }, - createMessagingInstance: async (request: CreateMessagingInstanceRequest) => { + createMessagingInstance: async (request: Types.CreateMessagingInstanceRequest) => { const response = await fetch(`${getApiBase()}/messaging/instances`, { method: "POST", headers: { "Content-Type": "application/json" }, @@ -2147,10 +1768,10 @@ export const api = { if (!response.ok) { throw new Error(`API error: ${response.status}`); } - return response.json() as Promise; + return response.json() as Promise; }, - deleteMessagingInstance: async (request: DeleteMessagingInstanceRequest) => { + deleteMessagingInstance: async (request: Types.DeleteMessagingInstanceRequest) => { const response = await fetch(`${getApiBase()}/messaging/instances`, { method: "DELETE", headers: { "Content-Type": "application/json" }, @@ -2159,13 +1780,13 @@ export const api = { if (!response.ok) { throw new Error(`API error: ${response.status}`); } - return response.json() as Promise; + return response.json() as Promise; }, // Global Settings API - globalSettings: () => fetchJson("/settings"), + globalSettings: () => fetchJson("/settings"), - updateGlobalSettings: async (settings: GlobalSettingsUpdate) => { + updateGlobalSettings: async (settings: Types.GlobalSettingsUpdate) => { const response = await fetch(`${getApiBase()}/settings`, { method: "PUT", headers: { "Content-Type": "application/json" }, @@ -2174,11 +1795,11 @@ export const api = { if (!response.ok) { throw new Error(`API error: ${response.status}`); } - return response.json() as Promise; + return response.json() as Promise; }, // Raw config API - rawConfig: () => fetchJson("/config/raw"), + rawConfig: () => fetchJson("/config/raw"), updateRawConfig: async (content: string) => { const response = await fetch(`${getApiBase()}/config/raw`, { method: "PUT", @@ -2188,7 +1809,7 @@ export const api = { if (!response.ok) { throw new Error(`API error: ${response.status}`); } - return response.json() as Promise; + return response.json() as Promise; }, // Changelog API @@ -2274,7 +1895,7 @@ export const api = { ), registrySkillContent: (source: string, skillId: string) => - fetchJson( + fetchJson( `/skills/registry/content?source=${encodeURIComponent(source)}&skill_id=${encodeURIComponent(skillId)}`, ), @@ -2639,5 +2260,17 @@ export const api = { return response.json() as Promise; }, + // TTS / Voice overlay methods (stubs) + ttsProfiles: async (_agentId: string): Promise<{ id: string; name: string }[]> => { + // TODO: Implement actual TTS profiles endpoint + return []; + }, + + webChatSendAudio: async (agentId: string, _sessionId: string, _blob: Blob): Promise => { + // TODO: Implement actual audio sending endpoint + console.warn("webChatSendAudio not implemented", agentId); + return new Response(null, { status: 501 }); + }, + getEventsUrl: () => `${getApiBase()}/events`, }; diff --git a/interface/src/api/schema.d.ts b/interface/src/api/schema.d.ts new file mode 100644 index 000000000..e5f92ef66 --- /dev/null +++ b/interface/src/api/schema.d.ts @@ -0,0 +1,8432 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +export interface paths { + "/agents": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List all configured agents with their config summaries. */ + get: operations["list_agents"]; + /** Update an agent's display_name and role in config.toml. */ + put: operations["update_agent"]; + /** Create a new agent and initialize it live (directories, databases, memory, identity, cron, cortex). */ + post: operations["create_agent"]; + /** Delete an agent: remove from config.toml, clean up API state, signal main loop. */ + delete: operations["delete_agent"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/agents/avatar": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Serve the agent's avatar image. */ + get: operations["get_avatar"]; + put?: never; + /** Upload (or replace) the agent's avatar image. */ + post: operations["upload_avatar"]; + /** Delete the agent's avatar image. */ + delete: operations["delete_avatar"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/agents/config": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get the resolved configuration for an agent. + * Reads live values from the agent's RuntimeConfig (hot-reloaded via ArcSwap). + */ + get: operations["get_agent_config"]; + /** + * Update agent configuration by editing config.toml with toml_edit. + * This preserves formatting and comments while writing the new values. + */ + put: operations["update_agent_config"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/agents/cron": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List all cron jobs for an agent with execution statistics. */ + get: operations["list_cron_jobs"]; + put?: never; + /** Create or update a cron job. */ + post: operations["create_or_update_cron"]; + /** Delete a cron job. */ + delete: operations["delete_cron"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/agents/cron/executions": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get execution history for cron jobs. */ + get: operations["cron_executions"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/agents/cron/toggle": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + /** Enable or disable a cron job. */ + put: operations["toggle_cron"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/agents/cron/trigger": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Trigger a cron job immediately. */ + post: operations["trigger_cron"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/agents/identity": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get identity files (SOUL.md, IDENTITY.md, ROLE.md) for an agent. */ + get: operations["get_identity"]; + /** + * Update identity files for an agent. Only writes files for fields that are present. + * The file watcher will pick up changes and hot-reload identity into RuntimeConfig. + */ + put: operations["update_identity"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/agents/ingest/files": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List ingested files with progress info for in-progress ones. */ + get: operations["list_ingest_files"]; + put?: never; + /** Upload one or more files to the agent's ingest directory. */ + post: operations["upload_ingest_file"]; + /** Delete a completed ingestion file record from history. */ + delete: operations["delete_ingest_file"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/agents/instance": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get instance-wide overview for the main dashboard. */ + get: operations["instance_overview"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/agents/mcp": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List MCP connection status for an agent. */ + get: operations["list_agent_mcp"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/agents/mcp/reconnect": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Force reconnect for a single MCP server on an agent. */ + post: operations["reconnect_agent_mcp"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/agents/memories": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List memories for an agent with sorting, filtering, and pagination. */ + get: operations["list_memories"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/agents/memories/graph": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get a subgraph of memories: nodes + all edges between them. */ + get: operations["memory_graph"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/agents/memories/graph/neighbors": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get the neighbors of a specific memory node. Returns new nodes + * and edges not already present in the client's graph. + */ + get: operations["memory_graph_neighbors"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/agents/memories/search": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Search memories using hybrid search (vector + FTS + graph). */ + get: operations["search_memories"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/agents/overview": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get overview stats for an agent: memory breakdown, channels, cron, cortex. */ + get: operations["agent_overview"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/agents/profile": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get the cortex-generated profile for an agent. */ + get: operations["get_agent_profile"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/agents/projects": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** GET /agents/projects — list projects for an agent. */ + get: operations["list_projects"]; + put?: never; + /** POST /agents/projects — create a new project. */ + post: operations["create_project"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/agents/projects/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** GET /agents/projects/{id} — get a project with repos and worktrees. */ + get: operations["get_project"]; + /** PUT /agents/projects/{id} — update a project. */ + put: operations["update_project"]; + post?: never; + /** DELETE /agents/projects/{id} — delete a project (DB records only). */ + delete: operations["delete_project"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/agents/projects/{id}/disk-usage": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** GET /agents/projects/{id}/disk-usage — calculate disk usage for a project. */ + get: operations["disk_usage"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/agents/projects/{id}/repos": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** POST /agents/projects/{id}/repos — add a repo to a project. */ + post: operations["create_repo"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/agents/projects/{id}/scan": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** POST /agents/projects/{id}/scan — re-scan project root for repos and worktrees. */ + post: operations["scan_project"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/agents/projects/{id}/worktrees": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** POST /agents/projects/{id}/worktrees — create a worktree. */ + post: operations["create_worktree"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/agents/projects/{project_id}/repos/{repo_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** DELETE /agents/projects/{project_id}/repos/{repo_id} — remove a repo. */ + delete: operations["delete_repo"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/agents/projects/{project_id}/worktrees/{worktree_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** DELETE /agents/projects/{project_id}/worktrees/{worktree_id} — remove a worktree. */ + delete: operations["delete_worktree"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/agents/skills": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List installed skills for an agent. */ + get: operations["list_skills"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/agents/skills/content": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get the full content of an installed skill. */ + get: operations["get_skill_content"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/agents/skills/install": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Install a skill from GitHub. */ + post: operations["install_skill"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/agents/skills/remove": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** Remove an installed skill. */ + delete: operations["remove_skill"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/agents/skills/upload": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Upload skill files (zip archives or directories) from the user's computer. */ + post: operations["upload_skill"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/agents/warmup": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get warmup status for one agent or all agents. */ + get: operations["get_warmup_status"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/agents/warmup/trigger": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Trigger warmup for one agent or all agents. */ + post: operations["trigger_warmup"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/bindings": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List all bindings, optionally filtered by agent_id. */ + get: operations["list_bindings"]; + /** Update an existing binding. */ + put: operations["update_binding"]; + /** Create a new binding (and optionally configure platform credentials). */ + post: operations["create_binding"]; + /** Delete a binding by matching agent_id + channel + platform-specific identifiers. */ + delete: operations["delete_binding"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/changelog": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Return the embedded CHANGELOG.md content. */ + get: operations["changelog"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/channels": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List channels across agents, with optional activity and agent filters. */ + get: operations["list_channels"]; + put?: never; + post?: never; + /** Delete a channel and its message history. */ + delete: operations["delete_channel"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/channels/archive": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Archive or unarchive a channel without deleting its history. */ + post: operations["set_channel_archive"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/channels/cancel-process": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Cancel a running worker or branch via the API. */ + post: operations["cancel_process"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/channels/messages": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get the unified timeline for a channel: messages, branch runs, and worker runs + * interleaved chronologically. + */ + get: operations["channel_messages"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/channels/prompt/capture": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Enable or disable prompt capture for a specific channel. */ + post: operations["set_prompt_capture"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/channels/prompt/inspect": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Render the full prompt that the LLM would see on the next turn for a + * given channel. Returns the rendered system prompt and conversation + * history — useful for debugging prompt construction, coalescing, + * status block content, and context window usage. + */ + get: operations["inspect_prompt"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/channels/prompt/snapshots": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List prompt snapshots for a channel (newest first). */ + get: operations["list_prompt_snapshots"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/channels/prompt/snapshots/get": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Retrieve a specific prompt snapshot. */ + get: operations["get_prompt_snapshot"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/channels/status": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get live status (active workers, branches, completed items) for all channels. */ + get: operations["channel_status"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/cortex/chat/messages": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Load persisted cortex chat history for a thread. + * If no thread_id is provided, loads the latest thread. + * If no threads exist, returns an empty list with a fresh thread_id. + */ + get: operations["cortex_chat_messages"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/cortex/chat/send": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Send a message to cortex chat. Returns an SSE stream with activity events. + * @description The stream emits: + * - `thinking` — cortex is processing + * - `tool_started` — a tool call began + * - `tool_completed` — a tool call finished (with result preview) + * - `done` — full response text + * - `error` — if something went wrong + */ + post: operations["cortex_chat_send"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/cortex/chat/threads": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List all cortex chat threads for an agent, newest first. */ + get: operations["cortex_chat_threads"]; + put?: never; + post?: never; + /** Delete a cortex chat thread and all its messages. */ + delete: operations["cortex_chat_delete_thread"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/cortex/events": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List cortex events for an agent with optional type filter, newest first. */ + get: operations["cortex_events"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/events": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** SSE endpoint streaming all agent events to connected clients. */ + get: operations["events_sse"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/factory/presets": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List all available preset archetypes (metadata only). */ + get: operations["list_presets"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/factory/presets/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Load a full preset by ID, including soul, identity, and role content. */ + get: operations["get_preset"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/health": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["health"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/idle": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Reports whether the instance is idle (no active workers or branches). + * Used by the platform to gate rolling updates. + */ + get: operations["idle"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/links": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List all links in the instance. */ + get: operations["list_links"]; + put?: never; + /** Create a new link between two agents. Persists to config.toml and updates in-memory state. */ + post: operations["create_link"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/links/agent/{agent_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get links for a specific agent. */ + get: operations["agent_links"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/links/groups": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List all groups. */ + get: operations["list_groups"]; + put?: never; + /** Create a visual agent group. */ + post: operations["create_group"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/links/groups/{group_name}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + /** Update a group by name. */ + put: operations["update_group"]; + post?: never; + /** Delete a group by name. */ + delete: operations["delete_group"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/links/humans": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List all humans. */ + get: operations["list_humans"]; + put?: never; + /** Create an org-level human. */ + post: operations["create_human"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/links/humans/{human_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + /** Update a human by ID. */ + put: operations["update_human"]; + post?: never; + /** Delete a human by ID. Also removes any links involving this human. */ + delete: operations["delete_human"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/links/{from}/{to}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + /** Update a link's properties. Identifies the link by from/to agent IDs in the path. */ + put: operations["update_link"]; + post?: never; + /** Delete a link between two agents. */ + delete: operations["delete_link"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/mcp/servers": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** GET /api/mcp/servers — list all configured MCP servers from config.toml. */ + get: operations["list_mcp_servers"]; + /** PUT /api/mcp/servers — update an existing MCP server definition. */ + put: operations["update_mcp_server"]; + /** POST /api/mcp/servers — add a new MCP server definition to config.toml. */ + post: operations["create_mcp_server"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/mcp/servers/{name}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** DELETE /api/mcp/servers/{name} — remove a server definition from config.toml. */ + delete: operations["delete_mcp_server"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/mcp/servers/{name}/reconnect": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** POST /api/mcp/servers/{name}/reconnect — force-reconnect a specific server. */ + post: operations["reconnect_mcp_server"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/mcp/status": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** GET /api/mcp/status — per-agent MCP connection status. */ + get: operations["mcp_status"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/messaging/disconnect": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Disconnect a messaging platform: remove credentials from config, remove all + * bindings for that platform, and shut down the adapter. + * @description When `adapter` is set, only the specified named instance is disconnected. + * When `adapter` is absent, the entire platform section (default + all named + * instances) is removed. + */ + post: operations["disconnect_platform"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/messaging/instances": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Create a new adapter instance for a platform. + * @description If `name` is `None`, creates/updates the default instance (root-level credentials). + * If `name` is `Some`, adds a `[[messaging..instances]]` entry. + * Starts the adapter at runtime and reloads bindings. + */ + post: operations["create_messaging_instance"]; + /** + * Delete a named adapter instance (or the default instance) for a platform. + * @description Removes the instance entry from config.toml, removes bindings targeting + * that adapter, and shuts down the runtime adapter. + */ + delete: operations["delete_messaging_instance"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/messaging/status": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get which messaging platforms are configured and enabled. */ + get: operations["messaging_status"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/messaging/toggle": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Toggle a messaging platform's enabled state. When disabling, shuts down the + * adapter. When enabling, reads credentials from config and hot-starts it. + */ + post: operations["toggle_platform"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/models": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["get_models"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/models/refresh": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["refresh_models"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/providers": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["get_providers"]; + put?: never; + post: operations["update_provider"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/providers/openai/browser-oauth/start": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["start_openai_browser_oauth"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/providers/openai/browser-oauth/status": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["openai_browser_oauth_status"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/providers/test-model": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["test_provider_model"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/providers/{provider}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete: operations["delete_provider"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/secrets": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** `GET /api/secrets` — List all secrets (name + category, no values). */ + get: operations["list_secrets"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/secrets/encrypt": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** `POST /api/secrets/encrypt` — Enable encryption. Returns the master key (hex). */ + post: operations["enable_encryption"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/secrets/export": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** `POST /api/secrets/export` — Export all secrets as a JSON backup. */ + post: operations["export_secrets"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/secrets/import": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** `POST /api/secrets/import` — Import secrets from a backup. */ + post: operations["import_secrets"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/secrets/lock": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** `POST /api/secrets/lock` — Lock encrypted store (clear key from memory). */ + post: operations["lock_secrets"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/secrets/migrate": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * `POST /api/secrets/migrate` — Auto-migrate literal keys from config.toml + * to the secret store. + * @description Scans the resolved config for plaintext credential values (not `env:` or + * `secret:` prefixed) and moves them to the secret store, replacing the + * config.toml entries with `secret:NAME` references. + */ + post: operations["migrate_secrets"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/secrets/rotate": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** `POST /api/secrets/rotate` — Rotate master key. */ + post: operations["rotate_key"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/secrets/status": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** `GET /api/secrets/status` — Store state, counts, encryption status. */ + get: operations["secrets_status"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/secrets/unlock": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** `POST /api/secrets/unlock` — Unlock encrypted store with master key (hex). */ + post: operations["unlock_secrets"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/secrets/{name}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + /** `PUT /api/secrets/:name` — Add or update a secret. */ + put: operations["put_secret"]; + post?: never; + /** `DELETE /api/secrets/:name` — Delete a secret. */ + delete: operations["delete_secret"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/secrets/{name}/info": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** `GET /api/secrets/:name/info` — Secret metadata (no value). */ + get: operations["secret_info"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/settings": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["get_global_settings"]; + put: operations["update_global_settings"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/settings/raw": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["get_raw_config"]; + put: operations["update_raw_config"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/skills/registry/browse": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Proxy browse requests to skills.sh leaderboard API. */ + get: operations["registry_browse"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/skills/registry/content": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Fetch the full SKILL.md content for a registry skill from GitHub. */ + get: operations["registry_skill_content"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/skills/registry/search": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Proxy search requests to skills.sh search API. */ + get: operations["registry_search"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/ssh/authorized-key": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + /** PUT /api/ssh/authorized-key — write the public key for SSH access. */ + put: operations["set_authorized_key"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/ssh/status": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** GET /api/ssh/status — check if SSH is available. */ + get: operations["ssh_status"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/status": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["status"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/system/backup/export": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["backup_export"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/system/backup/restore": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["backup_restore"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/system/storage": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["storage_status"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/tasks": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** `GET /tasks` — list tasks with optional filters. */ + get: operations["list_tasks"]; + put?: never; + /** `POST /tasks` — create a task. */ + post: operations["create_task"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/tasks/{number}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** `GET /tasks/{number}` — get a task by globally unique number. */ + get: operations["get_task"]; + /** `PUT /tasks/{number}` — update a task. */ + put: operations["update_task"]; + post?: never; + /** `DELETE /tasks/{number}` — delete a task. */ + delete: operations["delete_task"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/tasks/{number}/approve": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** `POST /tasks/{number}/approve` — approve a task (move to ready). */ + post: operations["approve_task"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/tasks/{number}/assign": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** `POST /tasks/{number}/assign` — reassign a task to a different agent. */ + post: operations["assign_task"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/tasks/{number}/execute": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * `POST /tasks/{number}/execute` — move a task to ready for execution. + * Tasks already in `ready` or `in_progress` are returned as-is. + */ + post: operations["execute_task"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/tools": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List the contents of the durable `tools/bin` directory. */ + get: operations["list_tools"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/topology": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get the full agent topology for graph rendering. */ + get: operations["topology"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/update-apply": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Pull the new Docker image and recreate this container. */ + post: operations["update_apply"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/update-check": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Return the current update status (from background check). */ + get: operations["update_check"]; + put?: never; + /** Force an immediate update check against GitHub. */ + post: operations["update_check_now"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/webchat/history": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["webchat_history"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/webchat/send": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Fire-and-forget message injection. The response arrives via the global SSE + * event bus (`/api/events`), same as every other channel. + */ + post: operations["webchat_send"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/workers": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List worker runs for an agent, with live status merged from StatusBlocks. */ + get: operations["list_workers"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/workers/detail": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get full detail for a single worker run, including decompressed transcript. */ + get: operations["worker_detail"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +} +export type webhooks = Record; +export interface components { + schemas: { + /** @description Content within an action step. */ + ActionContent: { + text: string; + /** @enum {string} */ + type: "text"; + } | { + args: string; + id: string; + name: string; + /** @enum {string} */ + type: "tool_call"; + }; + ActionResponse: { + message: string; + success: boolean; + }; + ActivityDayCount: { + /** Format: int64 */ + branches: number; + date: string; + /** Format: int64 */ + workers: number; + }; + AdapterInstanceStatus: { + binding_count: number; + configured: boolean; + enabled: boolean; + /** @description `None` means the default instance for the platform. */ + name?: string | null; + platform: string; + runtime_key: string; + }; + AgentConfigResponse: { + browser: components["schemas"]["BrowserSection"]; + channel: components["schemas"]["ChannelSection"]; + coalesce: components["schemas"]["CoalesceSection"]; + compaction: components["schemas"]["CompactionSection"]; + cortex: components["schemas"]["CortexSection"]; + discord: components["schemas"]["DiscordSection"]; + memory_persistence: components["schemas"]["MemoryPersistenceSection"]; + projects: components["schemas"]["ProjectsSection"]; + routing: components["schemas"]["RoutingSection"]; + sandbox: components["schemas"]["SandboxSection"]; + tuning: components["schemas"]["TuningSection"]; + warmup: components["schemas"]["WarmupSection"]; + }; + AgentConfigUpdateRequest: { + agent_id: string; + browser?: null | components["schemas"]["BrowserUpdate"]; + channel?: null | components["schemas"]["ChannelUpdate"]; + coalesce?: null | components["schemas"]["CoalesceUpdate"]; + compaction?: null | components["schemas"]["CompactionUpdate"]; + cortex?: null | components["schemas"]["CortexUpdate"]; + discord?: null | components["schemas"]["DiscordUpdate"]; + memory_persistence?: null | components["schemas"]["MemoryPersistenceUpdate"]; + projects?: null | components["schemas"]["ProjectsUpdate"]; + routing?: null | components["schemas"]["RoutingUpdate"]; + sandbox?: null | components["schemas"]["SandboxUpdate"]; + tuning?: null | components["schemas"]["TuningUpdate"]; + warmup?: null | components["schemas"]["WarmupUpdate"]; + }; + /** @description Summary of an agent's configuration, exposed via the API. */ + AgentInfo: { + context_window: number; + display_name?: string | null; + gradient_end?: string | null; + gradient_start?: string | null; + id: string; + max_concurrent_branches: number; + max_concurrent_workers: number; + max_turns: number; + role?: string | null; + workspace: string; + }; + AgentMcpResponse: { + servers: components["schemas"]["McpServerStatus"][]; + }; + AgentOverviewResponse: { + activity_daily: components["schemas"]["ActivityDayCount"][]; + activity_heatmap: components["schemas"]["HeatmapCell"][]; + channel_count: number; + cron_jobs: components["schemas"]["CronJobInfo"][]; + last_bulletin_at?: string | null; + latest_bulletin?: string | null; + memory_counts: { + [key: string]: number; + }; + memory_daily: components["schemas"]["DayCount"][]; + /** Format: int64 */ + memory_total: number; + recent_cortex_events: components["schemas"]["CortexEvent"][]; + }; + /** @description Persisted agent profile generated by the cortex. */ + AgentProfile: { + agent_id: string; + avatar_seed?: string | null; + bio?: string | null; + display_name?: string | null; + generated_at: string; + status?: string | null; + updated_at: string; + }; + AgentProfileResponse: { + profile?: null | components["schemas"]["AgentProfile"]; + }; + AgentSummary: { + activity_sparkline: number[]; + channel_count: number; + cron_job_count: number; + id: string; + last_activity_at?: string | null; + last_bulletin_at?: string | null; + /** Format: int64 */ + memory_total: number; + profile?: null | components["schemas"]["AgentProfile"]; + }; + AgentsResponse: { + agents: components["schemas"]["AgentInfo"][]; + }; + ApproveRequest: { + approved_by?: string | null; + }; + AssignRequest: { + assigned_agent_id: string; + }; + /** @description Association between memories. */ + Association: { + /** Format: date-time */ + created_at: string; + id: string; + relation_type: components["schemas"]["RelationType"]; + source_id: string; + target_id: string; + /** Format: float */ + weight: number; + }; + AuthorizedKeyRequest: { + public_key: string; + }; + AuthorizedKeyResponse: { + message: string; + success: boolean; + }; + BinaryEntry: { + modified?: string | null; + name: string; + /** Format: int64 */ + size: number; + }; + BindingResponse: { + adapter?: string | null; + agent_id: string; + channel: string; + channel_ids: string[]; + chat_id?: string | null; + dm_allowed_users: string[]; + guild_id?: string | null; + require_mention: boolean; + team_id?: string | null; + workspace_id?: string | null; + }; + BindingsListResponse: { + bindings: components["schemas"]["BindingResponse"][]; + }; + BrowserSection: { + close_policy: string; + enabled: boolean; + evaluate_enabled: boolean; + headless: boolean; + persist_session: boolean; + }; + BrowserUpdate: { + close_policy?: null | components["schemas"]["ClosePolicy"]; + enabled?: boolean | null; + evaluate_enabled?: boolean | null; + headless?: boolean | null; + persist_session?: boolean | null; + }; + CancelProcessRequest: { + channel_id: string; + process_id: string; + process_type: string; + }; + CancelProcessResponse: { + message: string; + success: boolean; + }; + ChannelResponse: { + agent_id: string; + created_at: string; + display_name?: string | null; + id: string; + is_active: boolean; + last_activity_at: string; + platform: string; + }; + ChannelSection: { + listen_only_mode: boolean; + }; + ChannelUpdate: { + listen_only_mode?: boolean | null; + }; + ChannelsResponse: { + channels: components["schemas"]["ChannelResponse"][]; + }; + /** + * @description What happens when a worker explicitly calls "close" on the browser. + * @enum {string} + */ + ClosePolicy: "close_browser" | "close_tabs" | "detach"; + CoalesceSection: { + /** Format: int64 */ + debounce_ms: number; + enabled: boolean; + /** Format: int64 */ + max_wait_ms: number; + min_messages: number; + multi_user_only: boolean; + }; + CoalesceUpdate: { + /** Format: int64 */ + debounce_ms?: number | null; + enabled?: boolean | null; + /** Format: int64 */ + max_wait_ms?: number | null; + min_messages?: number | null; + multi_user_only?: boolean | null; + }; + CompactionSection: { + /** Format: float */ + aggressive_threshold: number; + /** Format: float */ + background_threshold: number; + /** Format: float */ + emergency_threshold: number; + }; + CompactionUpdate: { + /** Format: float */ + aggressive_threshold?: number | null; + /** Format: float */ + background_threshold?: number | null; + /** Format: float */ + emergency_threshold?: number | null; + }; + CortexChatDeleteThreadRequest: { + agent_id: string; + thread_id: string; + }; + /** @description A persisted cortex chat message. */ + CortexChatMessage: { + channel_context?: string | null; + content: string; + created_at: string; + id: string; + role: string; + thread_id: string; + /** @description Serialized JSON array of tool calls (for assistant messages). */ + tool_calls?: components["schemas"]["CortexChatToolCall"][] | null; + }; + CortexChatMessagesResponse: { + messages: components["schemas"]["CortexChatMessage"][]; + thread_id: string; + }; + CortexChatSendRequest: { + agent_id: string; + channel_id?: string | null; + message: string; + thread_id: string; + }; + /** @description Summary of a cortex chat thread (returned by list_threads). */ + CortexChatThread: { + first_message_at: string; + last_message_at: string; + /** Format: int64 */ + message_count: number; + preview: string; + thread_id: string; + }; + CortexChatThreadsResponse: { + threads: components["schemas"]["CortexChatThread"][]; + }; + /** @description A tool call + result pair persisted alongside assistant messages. */ + CortexChatToolCall: { + args: string; + id: string; + result?: string | null; + status: string; + tool: string; + }; + /** @description A persisted cortex action record. */ + CortexEvent: { + created_at: string; + details?: unknown; + event_type: string; + id: string; + summary: string; + }; + CortexEventsResponse: { + events: components["schemas"]["CortexEvent"][]; + /** Format: int64 */ + total: number; + }; + CortexSection: { + /** Format: int64 */ + branch_timeout_secs: number; + /** Format: int64 */ + bulletin_interval_secs: number; + bulletin_max_turns: number; + bulletin_max_words: number; + /** Format: int32 */ + circuit_breaker_threshold: number; + /** Format: int32 */ + detached_worker_timeout_retry_limit: number; + /** Format: float */ + maintenance_decay_rate: number; + /** Format: int64 */ + maintenance_interval_secs: number; + /** Format: float */ + maintenance_merge_similarity_threshold: number; + /** Format: int64 */ + maintenance_min_age_days: number; + /** Format: float */ + maintenance_prune_threshold: number; + supervisor_kill_budget_per_tick: number; + /** Format: int64 */ + tick_interval_secs: number; + /** Format: int64 */ + worker_timeout_secs: number; + }; + CortexUpdate: { + /** Format: int64 */ + branch_timeout_secs?: number | null; + /** Format: int64 */ + bulletin_interval_secs?: number | null; + bulletin_max_turns?: number | null; + bulletin_max_words?: number | null; + /** Format: int32 */ + circuit_breaker_threshold?: number | null; + /** Format: int32 */ + detached_worker_timeout_retry_limit?: number | null; + /** Format: float */ + maintenance_decay_rate?: number | null; + /** Format: int64 */ + maintenance_interval_secs?: number | null; + /** Format: float */ + maintenance_merge_similarity_threshold?: number | null; + /** Format: int64 */ + maintenance_min_age_days?: number | null; + /** Format: float */ + maintenance_prune_threshold?: number | null; + supervisor_kill_budget_per_tick?: number | null; + /** Format: int64 */ + tick_interval_secs?: number | null; + /** Format: int64 */ + worker_timeout_secs?: number | null; + }; + CreateAgentRequest: { + agent_id: string; + display_name?: string | null; + role?: string | null; + }; + CreateBindingRequest: { + adapter?: string | null; + agent_id: string; + channel: string; + channel_ids?: string[]; + chat_id?: string | null; + dm_allowed_users?: string[]; + guild_id?: string | null; + platform_credentials?: null | components["schemas"]["PlatformCredentials"]; + require_mention?: boolean; + team_id?: string | null; + workspace_id?: string | null; + }; + CreateBindingResponse: { + message: string; + /** @description True if platform credentials were added/changed (adapter needs restart). */ + restart_required: boolean; + success: boolean; + }; + CreateCronRequest: { + /** Format: int32 */ + active_end_hour?: number | null; + /** Format: int32 */ + active_start_hour?: number | null; + agent_id: string; + cron_expr?: string | null; + delivery_target: string; + enabled?: boolean; + id: string; + /** Format: int64 */ + interval_secs?: number; + prompt: string; + run_once?: boolean; + /** Format: int64 */ + timeout_secs?: number | null; + }; + CreateGroupRequest: { + agent_ids?: string[]; + color?: string | null; + name: string; + }; + CreateHumanRequest: { + bio?: string | null; + description?: string | null; + discord_id?: string | null; + display_name?: string | null; + email?: string | null; + id: string; + role?: string | null; + slack_id?: string | null; + telegram_id?: string | null; + }; + CreateLinkRequest: { + direction?: string; + from: string; + kind?: string; + to: string; + }; + CreateMcpServerRequest: { + args?: string[]; + command?: string | null; + enabled?: boolean; + env?: { + [key: string]: string; + }; + headers?: { + [key: string]: string; + }; + name: string; + transport: string; + url?: string | null; + }; + CreateMessagingInstanceRequest: { + credentials?: components["schemas"]["InstanceCredentials"]; + enabled?: boolean | null; + name?: string | null; + platform: string; + }; + CreateProjectRequest: { + agent_id: string; + /** @description When true, scan root_path for git repos and register them automatically. */ + auto_discover?: boolean; + description?: string | null; + icon?: string | null; + name: string; + root_path: string; + settings?: unknown; + tags?: string[]; + }; + CreateRepoRequest: { + agent_id: string; + default_branch?: string | null; + description?: string | null; + name: string; + path: string; + remote_url?: string | null; + }; + CreateTaskRequest: { + /** @description Agent assigned to execute. Defaults to `owner_agent_id`. */ + assigned_agent_id?: string | null; + created_by?: string | null; + description?: string | null; + metadata?: unknown; + /** @description Agent that owns (created) this task. */ + owner_agent_id: string; + priority?: string | null; + source_memory_id?: string | null; + status?: string | null; + subtasks?: components["schemas"]["TaskSubtask"][]; + title: string; + }; + CreateWorktreeRequest: { + agent_id: string; + branch: string; + repo_id: string; + start_point?: string | null; + worktree_name?: string | null; + }; + CronActionResponse: { + message: string; + success: boolean; + }; + /** @description Entry in the cron execution log. */ + CronExecutionEntry: { + executed_at: string; + id: string; + result_summary?: string | null; + success: boolean; + }; + CronExecutionsResponse: { + executions: components["schemas"]["CronExecutionEntry"][]; + }; + CronJobInfo: { + active_hours?: [ + number, + number + ] | null; + cron_expr?: string | null; + delivery_target: string; + enabled: boolean; + id: string; + /** Format: int64 */ + interval_secs: number; + prompt: string; + run_once: boolean; + /** Format: int64 */ + timeout_secs?: number | null; + }; + CronJobWithStats: { + active_hours?: [ + number, + number + ] | null; + cron_expr?: string | null; + delivery_target: string; + enabled: boolean; + /** Format: int64 */ + failure_count: number; + id: string; + /** Format: int64 */ + interval_secs: number; + last_executed_at?: string | null; + prompt: string; + run_once: boolean; + /** Format: int64 */ + success_count: number; + /** Format: int64 */ + timeout_secs?: number | null; + }; + CronListResponse: { + jobs: components["schemas"]["CronJobWithStats"][]; + timezone: string; + }; + DayCount: { + /** Format: int64 */ + count: number; + date: string; + }; + DeleteBindingRequest: { + adapter?: string | null; + agent_id: string; + channel: string; + chat_id?: string | null; + guild_id?: string | null; + team_id?: string | null; + workspace_id?: string | null; + }; + DeleteBindingResponse: { + message: string; + success: boolean; + }; + DeleteMessagingInstanceRequest: { + name?: string | null; + platform: string; + }; + DeleteSecretResponse: { + deleted: string; + warning?: string | null; + }; + /** + * @description Deployment environment, detected from SPACEBOT_DEPLOYMENT env var. + * @enum {string} + */ + Deployment: "docker" | "hosted" | "native"; + DisconnectPlatformRequest: { + adapter?: string | null; + platform: string; + }; + DiscordSection: { + allow_bot_messages: boolean; + enabled: boolean; + }; + DiscordUpdate: { + allow_bot_messages?: boolean | null; + }; + DiskUsageEntry: { + /** Format: int64 */ + bytes: number; + is_dir: boolean; + name: string; + }; + DiskUsageResponse: { + entries: components["schemas"]["DiskUsageEntry"][]; + /** Format: int64 */ + total_bytes: number; + }; + EncryptResponse: { + master_key: string; + message: string; + }; + /** @description Portable backup format for all secrets in a store. */ + ExportData: { + /** + * @description Whether the source store had encryption enabled (informational only — + * values in this struct are always plaintext). + */ + encrypted: boolean; + /** @description All secrets with their metadata. */ + entries: components["schemas"]["ExportEntry"][]; + /** + * Format: int32 + * @description Format version (currently 1). + */ + version: number; + }; + /** @description A single secret in the export format. */ + ExportEntry: { + category: components["schemas"]["SecretCategory"]; + /** Format: date-time */ + created_at: string; + name: string; + /** Format: date-time */ + updated_at: string; + value: string; + }; + GlobalSettingsResponse: { + api_bind: string; + api_enabled: boolean; + /** Format: int32 */ + api_port: number; + brave_search_key?: string | null; + opencode: components["schemas"]["OpenCodeSettingsResponse"]; + ssh_enabled: boolean; + worker_log_mode: string; + }; + GlobalSettingsUpdate: { + api_bind?: string | null; + api_enabled?: boolean | null; + /** Format: int32 */ + api_port?: number | null; + brave_search_key?: string | null; + opencode?: null | components["schemas"]["OpenCodeSettingsUpdate"]; + ssh_enabled?: boolean | null; + worker_log_mode?: string | null; + }; + GlobalSettingsUpdateResponse: { + message: string; + requires_restart: boolean; + success: boolean; + }; + HealthResponse: { + status: string; + }; + HeatmapCell: { + /** Format: int64 */ + count: number; + /** Format: int64 */ + day: number; + /** Format: int64 */ + hour: number; + }; + IdentityResponse: { + identity?: string | null; + role?: string | null; + soul?: string | null; + }; + IdentityUpdateRequest: { + agent_id: string; + identity?: string | null; + role?: string | null; + soul?: string | null; + }; + IdleResponse: { + active_branches: number; + active_workers: number; + idle: boolean; + }; + ImportBody: components["schemas"]["ExportData"] & { + /** @description Whether to overwrite existing secrets with the same name. */ + overwrite?: boolean; + }; + IngestDeleteResponse: { + success: boolean; + }; + IngestFileInfo: { + /** Format: int64 */ + chunks_completed: number; + completed_at?: string | null; + content_hash: string; + /** Format: int64 */ + file_size: number; + filename: string; + started_at: string; + status: string; + /** Format: int64 */ + total_chunks: number; + }; + IngestFilesResponse: { + files: components["schemas"]["IngestFileInfo"][]; + }; + IngestUploadResponse: { + uploaded: string[]; + }; + InstallSkillRequest: { + agent_id: string; + instance?: boolean; + spec: string; + }; + InstallSkillResponse: { + installed: string[]; + }; + InstanceCredentials: { + discord_token?: string | null; + email_from_address?: string | null; + email_imap_host?: string | null; + email_imap_password?: string | null; + /** Format: int32 */ + email_imap_port?: number | null; + email_imap_username?: string | null; + email_smtp_host?: string | null; + email_smtp_password?: string | null; + /** Format: int32 */ + email_smtp_port?: number | null; + email_smtp_username?: string | null; + mattermost_base_url?: string | null; + mattermost_token?: string | null; + signal_account?: string | null; + signal_dm_allowed_users?: string | null; + signal_http_url?: string | null; + slack_app_token?: string | null; + slack_bot_token?: string | null; + telegram_token?: string | null; + twitch_client_id?: string | null; + twitch_client_secret?: string | null; + twitch_oauth_token?: string | null; + twitch_refresh_token?: string | null; + twitch_username?: string | null; + webhook_auth_token?: string | null; + webhook_bind?: string | null; + /** Format: int32 */ + webhook_port?: number | null; + }; + InstanceOverviewResponse: { + agents: components["schemas"]["AgentSummary"][]; + /** Format: int32 */ + pid: number; + /** Format: int64 */ + uptime_seconds: number; + version: string; + }; + McpAgentStatus: { + agent_id: string; + servers: components["schemas"]["McpServerInfo"][]; + }; + McpConnectionState: "connecting" | "connected" | { + failed: string; + } | "disconnected"; + McpServerInfo: { + enabled: boolean; + name: string; + state: string; + transport: string; + }; + McpServerStatus: { + enabled: boolean; + name: string; + state: components["schemas"]["McpConnectionState"]; + transport: string; + }; + MemoriesListResponse: { + memories: components["schemas"]["Memory"][]; + total: number; + }; + MemoriesSearchResponse: { + results: components["schemas"]["MemorySearchResult"][]; + }; + /** @description Memory structure. */ + Memory: { + /** Format: int64 */ + access_count: number; + channel_id?: string | null; + content: string; + /** Format: date-time */ + created_at: string; + /** + * @description Soft-delete flag. Forgotten memories are excluded from search and recall + * but remain in the database. + */ + forgotten: boolean; + id: string; + /** Format: float */ + importance: number; + /** Format: date-time */ + last_accessed_at: string; + memory_type: components["schemas"]["MemoryType"]; + source?: string | null; + /** Format: date-time */ + updated_at: string; + }; + MemoryGraphNeighborsResponse: { + edges: components["schemas"]["Association"][]; + nodes: components["schemas"]["Memory"][]; + }; + MemoryGraphResponse: { + edges: components["schemas"]["Association"][]; + nodes: components["schemas"]["Memory"][]; + total: number; + }; + MemoryPersistenceSection: { + enabled: boolean; + message_interval: number; + }; + MemoryPersistenceUpdate: { + enabled?: boolean | null; + message_interval?: number | null; + }; + /** @description Search result combining memory with relevance score. */ + MemorySearchResult: { + memory: components["schemas"]["Memory"]; + rank: number; + /** Format: float */ + score: number; + }; + /** + * @description Memory types. + * @enum {string} + */ + MemoryType: "fact" | "preference" | "decision" | "identity" | "event" | "observation" | "goal" | "todo"; + MessagesResponse: { + has_more: boolean; + items: components["schemas"]["TimelineItem"][]; + }; + MessagingInstanceActionResponse: { + message: string; + success: boolean; + }; + MessagingStatusResponse: { + discord: components["schemas"]["PlatformStatus"]; + email: components["schemas"]["PlatformStatus"]; + instances: components["schemas"]["AdapterInstanceStatus"][]; + mattermost: components["schemas"]["PlatformStatus"]; + signal: components["schemas"]["PlatformStatus"]; + slack: components["schemas"]["PlatformStatus"]; + telegram: components["schemas"]["PlatformStatus"]; + twitch: components["schemas"]["PlatformStatus"]; + webhook: components["schemas"]["PlatformStatus"]; + }; + MigrateResponse: { + message: string; + migrated: components["schemas"]["MigrationItem"][]; + skipped: string[]; + }; + MigrationItem: { + category: components["schemas"]["SecretCategory"]; + config_key: string; + secret_name: string; + }; + ModelInfo: { + /** + * Format: int64 + * @description Context window size in tokens, if known + */ + context_window?: number | null; + /** @description Full routing string (e.g. "openrouter/anthropic/claude-sonnet-4") */ + id: string; + /** @description Whether this model accepts audio input. */ + input_audio: boolean; + /** @description Human-readable name */ + name: string; + /** @description Provider ID for routing ("anthropic", "openrouter", "openai", etc.) */ + provider: string; + /** @description Whether this model has reasoning/thinking capability */ + reasoning: boolean; + /** @description Whether this model supports tool/function calling */ + tool_call: boolean; + }; + ModelsResponse: { + models: components["schemas"]["ModelInfo"][]; + }; + MutationResponse: { + message: string; + success: boolean; + }; + OpenAiOAuthBrowserStartRequest: { + model: string; + }; + OpenAiOAuthBrowserStartResponse: { + message: string; + state?: string | null; + success: boolean; + user_code?: string | null; + verification_url?: string | null; + }; + OpenAiOAuthBrowserStatusResponse: { + done: boolean; + found: boolean; + message?: string | null; + success: boolean; + }; + OpenCodePermissionsResponse: { + bash: string; + edit: string; + webfetch: string; + }; + OpenCodePermissionsUpdate: { + bash?: string | null; + edit?: string | null; + webfetch?: string | null; + }; + OpenCodeSettingsResponse: { + enabled: boolean; + /** Format: int32 */ + max_restart_retries: number; + max_servers: number; + path: string; + permissions: components["schemas"]["OpenCodePermissionsResponse"]; + /** Format: int64 */ + server_startup_timeout_secs: number; + }; + OpenCodeSettingsUpdate: { + enabled?: boolean | null; + /** Format: int32 */ + max_restart_retries?: number | null; + max_servers?: number | null; + path?: string | null; + permissions?: null | components["schemas"]["OpenCodePermissionsUpdate"]; + /** Format: int64 */ + server_startup_timeout_secs?: number | null; + }; + PlatformCredentials: { + discord_token?: string | null; + email_from_address?: string | null; + email_from_name?: string | null; + email_imap_host?: string | null; + email_imap_password?: string | null; + /** Format: int32 */ + email_imap_port?: number | null; + email_imap_username?: string | null; + email_smtp_host?: string | null; + email_smtp_password?: string | null; + /** Format: int32 */ + email_smtp_port?: number | null; + email_smtp_username?: string | null; + slack_app_token?: string | null; + slack_bot_token?: string | null; + telegram_token?: string | null; + twitch_client_id?: string | null; + twitch_client_secret?: string | null; + twitch_oauth_token?: string | null; + twitch_refresh_token?: string | null; + twitch_username?: string | null; + }; + PlatformStatus: { + configured: boolean; + enabled: boolean; + }; + /** @description A fully loaded preset with all identity file content. */ + Preset: { + identity: string; + meta: components["schemas"]["PresetMeta"]; + role: string; + soul: string; + }; + /** + * @description Default operational parameters suggested by a preset. + * + * Model routing is intentionally excluded — presets are provider-agnostic. + * The factory conversation handles model selection at creation time when the + * user's available providers are known. + */ + PresetDefaults: { + /** Format: int32 */ + max_concurrent_workers?: number | null; + /** Format: int32 */ + max_turns?: number | null; + }; + /** @description Metadata for a preset archetype (returned in list responses). */ + PresetMeta: { + defaults?: components["schemas"]["PresetDefaults"]; + description: string; + icon: string; + id: string; + name: string; + tags?: string[]; + }; + Project: { + agent_id: string; + created_at: string; + description: string; + icon: string; + id: string; + name: string; + root_path: string; + settings: unknown; + status: components["schemas"]["ProjectStatus"]; + tags: string[]; + updated_at: string; + }; + ProjectListResponse: { + projects: components["schemas"]["Project"][]; + }; + ProjectRepo: { + created_at: string; + /** @description Currently checked-out branch (may differ from `default_branch`). */ + current_branch?: string | null; + default_branch: string; + description: string; + /** Format: int64 */ + disk_usage_bytes?: number | null; + id: string; + name: string; + path: string; + project_id: string; + remote_url: string; + updated_at: string; + }; + ProjectResponse: components["schemas"]["ProjectWithRelations"]; + /** @enum {string} */ + ProjectStatus: "active" | "archived"; + /** @description Full project with nested repos and worktrees for API responses. */ + ProjectWithRelations: components["schemas"]["Project"] & { + repos: components["schemas"]["ProjectRepo"][]; + worktrees: components["schemas"]["ProjectWorktreeWithRepo"][]; + }; + ProjectWorktree: { + branch: string; + created_at: string; + created_by: string; + /** Format: int64 */ + disk_usage_bytes?: number | null; + id: string; + name: string; + path: string; + project_id: string; + repo_id: string; + updated_at: string; + }; + /** @description Worktree with the source repo name resolved. */ + ProjectWorktreeWithRepo: components["schemas"]["ProjectWorktree"] & { + repo_name: string; + }; + ProjectsSection: { + auto_create_worktrees: boolean; + auto_discover_repos: boolean; + auto_discover_worktrees: boolean; + /** Format: int64 */ + disk_usage_warning_threshold: number; + use_worktrees: boolean; + worktree_name_template: string; + }; + ProjectsUpdate: { + auto_create_worktrees?: boolean | null; + auto_discover_repos?: boolean | null; + auto_discover_worktrees?: boolean | null; + /** Format: int64 */ + disk_usage_warning_threshold?: number | null; + use_worktrees?: boolean | null; + worktree_name_template?: string | null; + }; + PromptCaptureBody: { + channel_id: string; + enabled: boolean; + }; + ProviderModelTestRequest: { + api_key: string; + model: string; + provider: string; + }; + ProviderModelTestResponse: { + message: string; + model: string; + provider: string; + sample?: string | null; + success: boolean; + }; + ProviderStatus: { + anthropic: boolean; + deepseek: boolean; + fireworks: boolean; + gemini: boolean; + github_copilot: boolean; + groq: boolean; + kilo: boolean; + minimax: boolean; + minimax_cn: boolean; + mistral: boolean; + moonshot: boolean; + nvidia: boolean; + ollama: boolean; + openai: boolean; + openai_chatgpt: boolean; + opencode_go: boolean; + opencode_zen: boolean; + openrouter: boolean; + together: boolean; + xai: boolean; + zai_coding_plan: boolean; + zhipu: boolean; + }; + ProviderUpdateRequest: { + api_key: string; + model: string; + provider: string; + }; + ProviderUpdateResponse: { + message: string; + success: boolean; + }; + ProvidersResponse: { + has_any: boolean; + providers: components["schemas"]["ProviderStatus"]; + }; + PutSecretBody: { + category?: null | components["schemas"]["SecretCategory"]; + value: string; + }; + PutSecretResponse: { + category: components["schemas"]["SecretCategory"]; + message: string; + name: string; + reload_required: boolean; + }; + RawConfigResponse: { + content: string; + }; + RawConfigUpdateRequest: { + content: string; + }; + RawConfigUpdateResponse: { + message: string; + success: boolean; + }; + ReconnectMcpRequest: { + agent_id: string; + server_name: string; + }; + RegistryBrowseResponse: { + has_more: boolean; + skills: components["schemas"]["RegistrySkill"][]; + /** Format: int64 */ + total?: number | null; + }; + RegistrySearchResponse: { + count: number; + query: string; + skills: components["schemas"]["RegistrySkill"][]; + }; + RegistrySkill: { + description?: string | null; + id?: string | null; + /** Format: int64 */ + installs: number; + name: string; + skillId: string; + source: string; + }; + RegistrySkillContentResponse: { + content?: string | null; + skill_id: string; + source: string; + }; + /** + * @description Relation types for memory associations. + * @enum {string} + */ + RelationType: "related_to" | "updates" | "contradicts" | "caused_by" | "result_of" | "part_of"; + RemoveSkillRequest: { + agent_id: string; + name: string; + }; + RemoveSkillResponse: { + path?: string | null; + success: boolean; + }; + RepoResponse: { + repo: components["schemas"]["ProjectRepo"]; + }; + RoutingSection: { + branch: string; + channel: string; + compactor: string; + cortex: string; + /** Format: int64 */ + rate_limit_cooldown_secs: number; + voice: string; + worker: string; + }; + RoutingUpdate: { + branch?: string | null; + channel?: string | null; + compactor?: string | null; + cortex?: string | null; + /** Format: int64 */ + rate_limit_cooldown_secs?: number | null; + voice?: string | null; + worker?: string | null; + }; + SandboxSection: { + mode: string; + passthrough_env: string[]; + writable_paths: string[]; + }; + SandboxUpdate: { + mode?: string | null; + passthrough_env?: string[] | null; + writable_paths?: string[] | null; + }; + /** + * @description Secret category determines subprocess exposure. + * + * All secrets are readable by Rust code via `SecretsStore::get()` regardless + * of category. The category answers one question: should this value be injected + * as an env var into worker subprocesses? + * @enum {string} + */ + SecretCategory: "system" | "tool"; + SecretInfoResponse: { + category: components["schemas"]["SecretCategory"]; + /** Format: date-time */ + created_at: string; + name: string; + /** Format: date-time */ + updated_at: string; + }; + SecretListItem: { + category: components["schemas"]["SecretCategory"]; + /** Format: date-time */ + created_at: string; + name: string; + /** Format: date-time */ + updated_at: string; + }; + SecretListResponse: { + secrets: components["schemas"]["SecretListItem"][]; + }; + SetChannelArchiveRequest: { + agent_id: string; + archived: boolean; + channel_id: string; + }; + SkillContentResponse: { + base_dir: string; + content: string; + description: string; + file_path: string; + name: string; + source: string; + source_repo?: string | null; + }; + SkillInfo: { + base_dir: string; + description: string; + file_path: string; + name: string; + source: string; + source_repo?: string | null; + }; + SkillsListResponse: { + skills: components["schemas"]["SkillInfo"][]; + }; + SshStatusResponse: { + enabled: boolean; + has_authorized_key: boolean; + /** Format: int32 */ + port: number; + }; + StatusResponse: { + /** Format: int32 */ + pid: number; + status: string; + /** Format: int64 */ + uptime_seconds: number; + version: string; + }; + StorageStatus: { + /** Format: int64 */ + available_bytes: number; + /** Format: int64 */ + total_bytes: number; + /** Format: int64 */ + used_bytes: number; + }; + Task: { + approved_at?: string | null; + approved_by?: string | null; + assigned_agent_id: string; + completed_at?: string | null; + created_at: string; + created_by: string; + description?: string | null; + id: string; + metadata: unknown; + owner_agent_id: string; + priority: components["schemas"]["TaskPriority"]; + source_memory_id?: string | null; + status: components["schemas"]["TaskStatus"]; + subtasks: components["schemas"]["TaskSubtask"][]; + /** Format: int64 */ + task_number: number; + title: string; + updated_at: string; + worker_id?: string | null; + }; + TaskActionResponse: { + message: string; + success: boolean; + }; + TaskListResponse: { + tasks: components["schemas"]["Task"][]; + }; + /** @enum {string} */ + TaskPriority: "critical" | "high" | "medium" | "low"; + TaskResponse: { + task: components["schemas"]["Task"]; + }; + /** @enum {string} */ + TaskStatus: "pending_approval" | "backlog" | "ready" | "in_progress" | "done"; + TaskSubtask: { + completed: boolean; + title: string; + }; + /** @description A unified timeline item combining messages, branch runs, and worker runs. */ + TimelineItem: { + content: string; + created_at: string; + id: string; + role: string; + sender_id?: string | null; + sender_name?: string | null; + /** @enum {string} */ + type: "message"; + } | { + completed_at?: string | null; + conclusion?: string | null; + description: string; + id: string; + started_at: string; + /** @enum {string} */ + type: "branch_run"; + } | { + completed_at?: string | null; + id: string; + result?: string | null; + started_at: string; + status: string; + task: string; + /** @enum {string} */ + type: "worker_run"; + }; + ToggleCronRequest: { + agent_id: string; + cron_id: string; + enabled: boolean; + }; + TogglePlatformRequest: { + adapter?: string | null; + enabled: boolean; + platform: string; + }; + ToolsResponse: { + binaries: components["schemas"]["BinaryEntry"][]; + tools_bin: string; + }; + TopologyAgent: { + display_name?: string | null; + id: string; + name: string; + role?: string | null; + }; + TopologyGroup: { + agent_ids: string[]; + color?: string | null; + name: string; + }; + TopologyHuman: { + bio?: string | null; + description?: string | null; + discord_id?: string | null; + display_name?: string | null; + email?: string | null; + id: string; + role?: string | null; + slack_id?: string | null; + telegram_id?: string | null; + }; + TopologyLink: { + direction: string; + from: string; + kind: string; + to: string; + }; + /** @description Topology response for graph rendering. */ + TopologyResponse: { + agents: components["schemas"]["TopologyAgent"][]; + groups: components["schemas"]["TopologyGroup"][]; + humans: components["schemas"]["TopologyHuman"][]; + links: components["schemas"]["TopologyLink"][]; + }; + /** @description A single step in a worker transcript. */ + TranscriptStep: { + content: components["schemas"]["ActionContent"][]; + /** @enum {string} */ + type: "action"; + } | { + text: string; + /** @enum {string} */ + type: "user_text"; + } | { + text: string; + /** @enum {string} */ + type: "system_text"; + } | { + call_id: string; + name: string; + text: string; + /** @enum {string} */ + type: "tool_result"; + }; + TriggerCronRequest: { + agent_id: string; + cron_id: string; + }; + TuningSection: { + branch_max_turns: number; + context_window: number; + history_backfill_count: number; + max_concurrent_branches: number; + max_concurrent_workers: number; + max_turns: number; + }; + TuningUpdate: { + branch_max_turns?: number | null; + context_window?: number | null; + history_backfill_count?: number | null; + max_concurrent_branches?: number | null; + max_concurrent_workers?: number | null; + max_turns?: number | null; + }; + UnlockBody: { + master_key: string; + }; + UpdateAgentRequest: { + agent_id: string; + display_name?: string | null; + gradient_end?: string | null; + gradient_start?: string | null; + role?: string | null; + }; + UpdateBindingRequest: { + adapter?: string | null; + agent_id: string; + channel: string; + channel_ids?: string[]; + chat_id?: string | null; + dm_allowed_users?: string[]; + guild_id?: string | null; + original_adapter?: string | null; + original_agent_id: string; + original_channel: string; + original_chat_id?: string | null; + original_guild_id?: string | null; + original_team_id?: string | null; + original_workspace_id?: string | null; + require_mention?: boolean; + team_id?: string | null; + workspace_id?: string | null; + }; + UpdateBindingResponse: { + message: string; + success: boolean; + }; + UpdateGroupRequest: { + agent_ids?: string[] | null; + color?: string | null; + name?: string | null; + }; + UpdateHumanRequest: { + bio?: string | null; + description?: string | null; + discord_id?: string | null; + display_name?: string | null; + email?: string | null; + role?: string | null; + slack_id?: string | null; + telegram_id?: string | null; + }; + UpdateLinkRequest: { + direction?: string | null; + kind?: string | null; + }; + UpdateProjectRequest: { + agent_id: string; + description?: string | null; + icon?: string | null; + name?: string | null; + settings?: unknown; + status?: string | null; + tags?: string[] | null; + }; + /** @description Result of an update check. */ + UpdateStatus: { + /** @description Whether the Docker socket is accessible (enables one-click update). */ + can_apply: boolean; + /** @description Human-readable reason when one-click apply is unavailable. */ + cannot_apply_reason?: string | null; + /** Format: date-time */ + checked_at?: string | null; + current_version: string; + deployment: components["schemas"]["Deployment"]; + /** @description Current container image reference when running in Docker. */ + docker_image?: string | null; + error?: string | null; + latest_version?: string | null; + release_notes?: string | null; + release_url?: string | null; + update_available: boolean; + }; + UpdateTaskRequest: { + approved_by?: string | null; + assigned_agent_id?: string | null; + complete_subtask?: number | null; + description?: string | null; + metadata?: unknown; + priority?: string | null; + status?: string | null; + subtasks?: components["schemas"]["TaskSubtask"][] | null; + title?: string | null; + worker_id?: string | null; + }; + UploadSkillResponse: { + installed: string[]; + }; + WarmupSection: { + eager_embedding_load: boolean; + enabled: boolean; + /** Format: int64 */ + refresh_secs: number; + /** Format: int64 */ + startup_delay_secs: number; + }; + /** + * @description Current warmup lifecycle state. + * @enum {string} + */ + WarmupState: "cold" | "warming" | "warm" | "degraded"; + /** @description Warmup runtime status snapshot for API and observability. */ + WarmupStatus: { + /** Format: int64 */ + bulletin_age_secs?: number | null; + embedding_ready: boolean; + last_error?: string | null; + /** Format: int64 */ + last_refresh_unix_ms?: number | null; + state: components["schemas"]["WarmupState"]; + }; + WarmupStatusEntry: { + agent_id: string; + status: components["schemas"]["WarmupStatus"]; + }; + WarmupStatusResponse: { + statuses: components["schemas"]["WarmupStatusEntry"][]; + }; + WarmupTriggerRequest: { + agent_id?: string | null; + force?: boolean; + }; + WarmupTriggerResponse: { + accepted_agents: string[]; + forced: boolean; + status: string; + }; + WarmupUpdate: { + eager_embedding_load?: boolean | null; + enabled?: boolean | null; + /** Format: int64 */ + refresh_secs?: number | null; + /** Format: int64 */ + startup_delay_secs?: number | null; + }; + WebChatHistoryMessage: { + content: string; + id: string; + role: string; + }; + WebChatSendRequest: { + agent_id: string; + message: string; + sender_name?: string; + session_id: string; + }; + WebChatSendResponse: { + ok: boolean; + }; + WorkerDetailResponse: { + channel_id?: string | null; + channel_name?: string | null; + completed_at?: string | null; + /** @description Working directory for OpenCode workers. */ + directory?: string | null; + id: string; + /** @description Whether this worker accepts follow-up input via route. */ + interactive: boolean; + /** + * Format: int32 + * @description OpenCode server port (for workers with an embeddable web UI). + */ + opencode_port?: number | null; + /** @description OpenCode session ID (for workers with an embeddable web UI). */ + opencode_session_id?: string | null; + result?: string | null; + started_at: string; + status: string; + task: string; + /** Format: int64 */ + tool_calls: number; + transcript?: components["schemas"]["TranscriptStep"][] | null; + worker_type: string; + }; + WorkerListItem: { + channel_id?: string | null; + channel_name?: string | null; + completed_at?: string | null; + /** @description Working directory for OpenCode workers. */ + directory?: string | null; + has_transcript: boolean; + id: string; + /** @description Whether this worker accepts follow-up input via route. */ + interactive: boolean; + /** @description Live status text from StatusBlock (running workers only). */ + live_status?: string | null; + /** + * Format: int32 + * @description OpenCode server port (for workers with an embeddable web UI). + */ + opencode_port?: number | null; + /** @description OpenCode session ID (for workers with an embeddable web UI). */ + opencode_session_id?: string | null; + /** @description Project ID this worker is linked to. */ + project_id?: string | null; + /** @description Project name (resolved via join). */ + project_name?: string | null; + started_at: string; + status: string; + task: string; + /** + * Format: int64 + * @description Total tool calls. From DB for completed workers, from StatusBlock for running. + */ + tool_calls: number; + worker_type: string; + }; + WorkerListResponse: { + /** Format: int64 */ + total: number; + workers: components["schemas"]["WorkerListItem"][]; + }; + WorktreeResponse: { + worktree: components["schemas"]["ProjectWorktree"]; + }; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} +export type $defs = Record; +export interface operations { + list_agents: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AgentsResponse"]; + }; + }; + }; + }; + update_agent: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["UpdateAgentRequest"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Agent not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + create_agent: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreateAgentRequest"]; + }; + }; + responses: { + /** @description Agent created successfully */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Invalid request or agent limit reached */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Agent already exists */ + 409: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + delete_agent: { + parameters: { + query: { + /** @description Agent ID to delete */ + agent_id: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + get_avatar: { + parameters: { + query: { + /** @description Agent ID */ + agent_id: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Avatar image */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Avatar or agent not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + upload_avatar: { + parameters: { + query: { + /** @description Agent ID */ + agent_id: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Avatar uploaded successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Unsupported image type */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Agent not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Payload too large */ + 413: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + delete_avatar: { + parameters: { + query: { + /** @description Agent ID */ + agent_id: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Agent not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + get_agent_config: { + parameters: { + query: { + agent_id: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AgentConfigResponse"]; + }; + }; + /** @description Agent not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + update_agent_config: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["AgentConfigUpdateRequest"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AgentConfigResponse"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + list_cron_jobs: { + parameters: { + query: { + /** @description Agent ID */ + agent_id: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CronListResponse"]; + }; + }; + /** @description Agent not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + create_or_update_cron: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreateCronRequest"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CronActionResponse"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Agent not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + delete_cron: { + parameters: { + query: { + /** @description Agent ID */ + agent_id: string; + /** @description Cron job ID to delete */ + cron_id: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CronActionResponse"]; + }; + }; + /** @description Agent not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + cron_executions: { + parameters: { + query: { + /** @description Agent ID */ + agent_id: string; + /** @description Cron job ID (optional) */ + cron_id?: string; + /** @description Maximum number of executions to return (default 50) */ + limit: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CronExecutionsResponse"]; + }; + }; + /** @description Agent not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + toggle_cron: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ToggleCronRequest"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CronActionResponse"]; + }; + }; + /** @description Agent not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + trigger_cron: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["TriggerCronRequest"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CronActionResponse"]; + }; + }; + /** @description Agent not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + get_identity: { + parameters: { + query: { + /** @description Agent ID */ + agent_id: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["IdentityResponse"]; + }; + }; + /** @description Agent not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + update_identity: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["IdentityUpdateRequest"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["IdentityResponse"]; + }; + }; + /** @description Agent not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + list_ingest_files: { + parameters: { + query: { + /** @description Agent ID */ + agent_id: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["IngestFilesResponse"]; + }; + }; + /** @description Agent not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + upload_ingest_file: { + parameters: { + query: { + /** @description Agent ID */ + agent_id: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["IngestUploadResponse"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Agent not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + delete_ingest_file: { + parameters: { + query: { + /** @description Agent ID */ + agent_id: string; + /** @description Content hash of the file to delete */ + content_hash: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["IngestDeleteResponse"]; + }; + }; + /** @description Agent not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + instance_overview: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["InstanceOverviewResponse"]; + }; + }; + }; + }; + list_agent_mcp: { + parameters: { + query: { + /** @description Agent ID */ + agent_id: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AgentMcpResponse"]; + }; + }; + /** @description Agent not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + reconnect_agent_mcp: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ReconnectMcpRequest"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Failed to reconnect */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Agent not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + list_memories: { + parameters: { + query: { + /** @description Agent ID */ + agent_id: string; + /** @description Maximum number of results to return (default 50, max 200) */ + limit: number; + /** @description Number of results to skip for pagination */ + offset: number; + /** @description Filter by memory type (fact, preference, decision, identity, event, observation, goal, todo) */ + memory_type?: string; + /** @description Sort order: recent, importance, most_accessed (default: recent) */ + sort: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MemoriesListResponse"]; + }; + }; + /** @description Agent not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + memory_graph: { + parameters: { + query: { + /** @description Agent ID */ + agent_id: string; + /** @description Maximum number of nodes to return (default 200, max 500) */ + limit: number; + /** @description Number of nodes to skip for pagination */ + offset: number; + /** @description Filter by memory type */ + memory_type?: string; + /** @description Sort order: recent, importance, most_accessed (default: recent) */ + sort: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MemoryGraphResponse"]; + }; + }; + /** @description Agent not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + memory_graph_neighbors: { + parameters: { + query: { + /** @description Agent ID */ + agent_id: string; + /** @description Memory ID to get neighbors for */ + memory_id: string; + /** @description Neighbor traversal depth (default 1, max 3) */ + depth: number; + /** @description Comma-separated list of memory IDs to exclude from results */ + exclude?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MemoryGraphNeighborsResponse"]; + }; + }; + /** @description Agent not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + search_memories: { + parameters: { + query: { + /** @description Agent ID */ + agent_id: string; + /** @description Search query string */ + q: string; + /** @description Maximum number of results to return (default 20, max 100) */ + limit: number; + /** @description Filter by memory type */ + memory_type?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MemoriesSearchResponse"]; + }; + }; + /** @description Agent not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + agent_overview: { + parameters: { + query: { + /** @description Agent ID */ + agent_id: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AgentOverviewResponse"]; + }; + }; + /** @description Agent not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + get_agent_profile: { + parameters: { + query: { + /** @description Agent ID */ + agent_id: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AgentProfileResponse"]; + }; + }; + /** @description Agent not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + list_projects: { + parameters: { + query: { + agent_id: string; + status?: string | null; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProjectListResponse"]; + }; + }; + /** @description Agent not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + create_project: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreateProjectRequest"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProjectResponse"]; + }; + }; + /** @description Agent not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + get_project: { + parameters: { + query: { + agent_id: string; + }; + header?: never; + path: { + /** @description Project ID */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProjectResponse"]; + }; + }; + /** @description Agent or project not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + update_project: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Project ID */ + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["UpdateProjectRequest"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProjectResponse"]; + }; + }; + /** @description Agent or project not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + delete_project: { + parameters: { + query: { + agent_id: string; + }; + header?: never; + path: { + /** @description Project ID */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ActionResponse"]; + }; + }; + /** @description Agent or project not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + disk_usage: { + parameters: { + query: { + agent_id: string; + }; + header?: never; + path: { + /** @description Project ID */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DiskUsageResponse"]; + }; + }; + /** @description Agent or project not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + create_repo: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Project ID */ + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreateRepoRequest"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RepoResponse"]; + }; + }; + /** @description Agent or project not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + scan_project: { + parameters: { + query: { + agent_id: string; + }; + header?: never; + path: { + /** @description Project ID */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProjectResponse"]; + }; + }; + /** @description Agent or project not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + create_worktree: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Project ID */ + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreateWorktreeRequest"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["WorktreeResponse"]; + }; + }; + /** @description Agent, project, or repo not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + delete_repo: { + parameters: { + query: { + agent_id: string; + }; + header?: never; + path: { + /** @description Project ID */ + project_id: string; + /** @description Repository ID */ + repo_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ActionResponse"]; + }; + }; + /** @description Agent, project, or repo not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + delete_worktree: { + parameters: { + query: { + agent_id: string; + }; + header?: never; + path: { + /** @description Project ID */ + project_id: string; + /** @description Worktree ID */ + worktree_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ActionResponse"]; + }; + }; + /** @description Agent, project, or worktree not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + list_skills: { + parameters: { + query: { + /** @description Agent ID */ + agent_id: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SkillsListResponse"]; + }; + }; + /** @description Agent not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + get_skill_content: { + parameters: { + query: { + /** @description Agent ID */ + agent_id: string; + /** @description Skill name */ + name: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SkillContentResponse"]; + }; + }; + /** @description Agent or skill not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + install_skill: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["InstallSkillRequest"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["InstallSkillResponse"]; + }; + }; + /** @description Agent not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + remove_skill: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["RemoveSkillRequest"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RemoveSkillResponse"]; + }; + }; + /** @description Cannot remove instance-level skill */ + 403: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Agent not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + upload_skill: { + parameters: { + query: { + /** @description Agent ID */ + agent_id: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["UploadSkillResponse"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Agent not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + get_warmup_status: { + parameters: { + query?: { + /** @description Optional agent ID to get status for a specific agent */ + agent_id?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["WarmupStatusResponse"]; + }; + }; + /** @description Agent not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + trigger_warmup: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["WarmupTriggerRequest"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["WarmupTriggerResponse"]; + }; + }; + /** @description Agent not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description LLM manager not available */ + 503: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + list_bindings: { + parameters: { + query?: { + /** @description Filter by agent ID */ + agent_id?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BindingsListResponse"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + update_binding: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["UpdateBindingRequest"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["UpdateBindingResponse"]; + }; + }; + /** @description Binding not found or config not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + create_binding: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreateBindingRequest"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CreateBindingResponse"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + delete_binding: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["DeleteBindingRequest"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DeleteBindingResponse"]; + }; + }; + /** @description Binding not found or config not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + changelog: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + list_channels: { + parameters: { + query: { + /** @description Include inactive channels */ + include_inactive: boolean; + /** @description Filter by agent ID */ + agent_id?: string; + /** @description Filter by active state */ + is_active?: boolean; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ChannelsResponse"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + delete_channel: { + parameters: { + query: { + /** @description Agent ID that owns the channel */ + agent_id: string; + /** @description Channel ID to delete */ + channel_id: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Channel or agent not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + set_channel_archive: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SetChannelArchiveRequest"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Channel or agent not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + cancel_process: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CancelProcessRequest"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CancelProcessResponse"]; + }; + }; + /** @description Invalid process type or process ID */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Process or channel not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + channel_messages: { + parameters: { + query: { + /** @description Channel ID */ + channel_id: string; + /** @description Maximum number of messages to return (default: 20, max: 100) */ + limit: number; + /** @description Pagination cursor for fetching older messages */ + before?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessagesResponse"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + set_prompt_capture: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["PromptCaptureBody"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Agent or settings not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + inspect_prompt: { + parameters: { + query: { + /** @description Channel ID to inspect */ + channel_id: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Channel not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + list_prompt_snapshots: { + parameters: { + query: { + /** @description Channel ID to list snapshots for */ + channel_id: string; + /** @description Maximum number of snapshots to return (default: 50) */ + limit: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Snapshot store not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + get_prompt_snapshot: { + parameters: { + query: { + /** @description Channel ID the snapshot belongs to */ + channel_id: string; + /** @description Snapshot timestamp in milliseconds */ + timestamp_ms: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Snapshot or store not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + channel_status: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + cortex_chat_messages: { + parameters: { + query: { + /** @description Agent ID */ + agent_id: string; + /** @description Thread ID (omit for latest) */ + thread_id?: string; + /** @description Maximum messages to return (default: 50, max: 200) */ + limit: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CortexChatMessagesResponse"]; + }; + }; + /** @description Agent not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + cortex_chat_send: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CortexChatSendRequest"]; + }; + }; + responses: { + /** @description SSE stream of chat events */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Agent not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Cortex chat session busy */ + 409: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + cortex_chat_threads: { + parameters: { + query: { + /** @description Agent ID */ + agent_id: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CortexChatThreadsResponse"]; + }; + }; + /** @description Agent not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + cortex_chat_delete_thread: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CortexChatDeleteThreadRequest"]; + }; + }; + responses: { + /** @description Thread deleted successfully */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Agent or thread not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + cortex_events: { + parameters: { + query: { + /** @description Agent ID */ + agent_id: string; + /** @description Maximum events to return (default: 50, max: 200) */ + limit: number; + /** @description Offset for pagination (default: 0) */ + offset: number; + /** @description Filter by event type */ + event_type?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CortexEventsResponse"]; + }; + }; + /** @description Agent not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + events_sse: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description SSE event stream */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "text/event-stream": unknown; + }; + }; + }; + }; + list_presets: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PresetMeta"][]; + }; + }; + }; + }; + get_preset: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Preset ID */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Preset"]; + }; + }; + /** @description Preset not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + health: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HealthResponse"]; + }; + }; + }; + }; + idle: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["IdleResponse"]; + }; + }; + }; + }; + list_links: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + create_link: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreateLinkRequest"]; + }; + }; + responses: { + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Agent or human not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Link already exists */ + 409: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + agent_links: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Agent ID */ + agent_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + list_groups: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + create_group: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreateGroupRequest"]; + }; + }; + responses: { + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Group already exists */ + 409: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + update_group: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Group name */ + group_name: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["UpdateGroupRequest"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Group not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Group name conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + delete_group: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Group name */ + group_name: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Group not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + list_humans: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + create_human: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreateHumanRequest"]; + }; + }; + responses: { + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Human ID already exists */ + 409: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + update_human: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Human ID */ + human_id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["UpdateHumanRequest"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Human not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + delete_human: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Human ID */ + human_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Human not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + update_link: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Source agent */ + from: string; + /** @description Target agent */ + to: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["UpdateLinkRequest"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Link not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + delete_link: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Source agent */ + from: string; + /** @description Target agent */ + to: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Link not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + list_mcp_servers: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["McpServerInfo"][]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + update_mcp_server: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreateMcpServerRequest"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MutationResponse"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + create_mcp_server: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreateMcpServerRequest"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MutationResponse"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + delete_mcp_server: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Server name */ + name: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MutationResponse"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + reconnect_mcp_server: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Server name */ + name: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MutationResponse"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + mcp_status: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["McpAgentStatus"][]; + }; + }; + }; + }; + disconnect_platform: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["DisconnectPlatformRequest"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + create_messaging_instance: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreateMessagingInstanceRequest"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessagingInstanceActionResponse"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + delete_messaging_instance: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["DeleteMessagingInstanceRequest"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessagingInstanceActionResponse"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + messaging_status: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessagingStatusResponse"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + toggle_platform: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["TogglePlatformRequest"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + get_models: { + parameters: { + query?: { + /** @description Filter by provider ID */ + provider?: string; + /** @description Filter by capability (input_audio, voice_transcription) */ + capability?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ModelsResponse"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + refresh_models: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ModelsResponse"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + get_providers: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProvidersResponse"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + update_provider: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ProviderUpdateRequest"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProviderUpdateResponse"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + start_openai_browser_oauth: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["OpenAiOAuthBrowserStartRequest"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["OpenAiOAuthBrowserStartResponse"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + openai_browser_oauth_status: { + parameters: { + query: { + /** @description OAuth state parameter */ + state: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["OpenAiOAuthBrowserStatusResponse"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + test_provider_model: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ProviderModelTestRequest"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProviderModelTestResponse"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + delete_provider: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Provider name to delete */ + provider: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProviderUpdateResponse"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Provider not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + list_secrets: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SecretListResponse"]; + }; + }; + /** @description Secrets store not initialized */ + 503: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + enable_encryption: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["EncryptResponse"]; + }; + }; + /** @description Encryption already enabled */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Secrets store not initialized */ + 503: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + export_secrets: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Secret store is locked */ + 423: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Secrets store not initialized */ + 503: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + import_secrets: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ImportBody"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Secret store is locked */ + 423: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Secrets store not initialized */ + 503: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + lock_secrets: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Lock failed */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Secrets store not initialized */ + 503: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + migrate_secrets: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MigrateResponse"]; + }; + }; + /** @description Secret store is locked */ + 423: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Secrets store not initialized */ + 503: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + rotate_key: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Key rotation failed */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Secrets store not initialized */ + 503: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + secrets_status: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Secrets store not initialized */ + 503: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + unlock_secrets: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["UnlockBody"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Invalid master key format */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Invalid master key */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Secrets store not initialized */ + 503: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + put_secret: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Secret name */ + name: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["PutSecretBody"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PutSecretResponse"]; + }; + }; + /** @description Secret store is locked */ + 423: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Secrets store not initialized */ + 503: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + delete_secret: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Secret name */ + name: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DeleteSecretResponse"]; + }; + }; + /** @description Secret store is locked */ + 423: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Secrets store not initialized */ + 503: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + secret_info: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Secret name */ + name: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SecretInfoResponse"]; + }; + }; + /** @description Secret not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Secrets store not initialized */ + 503: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + get_global_settings: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["GlobalSettingsResponse"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + update_global_settings: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["GlobalSettingsUpdate"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["GlobalSettingsUpdateResponse"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + get_raw_config: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RawConfigResponse"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + update_raw_config: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["RawConfigUpdateRequest"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RawConfigUpdateResponse"]; + }; + }; + /** @description Validation error */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + registry_browse: { + parameters: { + query: { + /** @description View type (all-time, trending, hot) */ + view: string; + /** @description Page number */ + page: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RegistryBrowseResponse"]; + }; + }; + /** @description Bad gateway */ + 502: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + registry_skill_content: { + parameters: { + query: { + /** @description GitHub owner/repo */ + source: string; + /** @description Skill identifier within the repo */ + skill_id: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RegistrySkillContentResponse"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + registry_search: { + parameters: { + query: { + /** @description Search query */ + q: string; + /** @description Result limit */ + limit: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RegistrySearchResponse"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Bad gateway */ + 502: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + set_authorized_key: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["AuthorizedKeyRequest"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AuthorizedKeyResponse"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + ssh_status: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SshStatusResponse"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + status: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["StatusResponse"]; + }; + }; + }; + }; + backup_export: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Backup archive */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/zip": unknown; + }; + }; + /** @description No runtime config available */ + 503: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + backup_restore: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/octet-stream": number[]; + }; + }; + responses: { + /** @description Backup restored successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Empty payload */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description No runtime config available */ + 503: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + storage_status: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["StorageStatus"]; + }; + }; + /** @description No runtime config available */ + 503: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + list_tasks: { + parameters: { + query?: { + /** @description Convenience filter: matches tasks where owner OR assigned equals this value. */ + agent_id?: string | null; + /** @description Filter by owner agent. Optional. */ + owner_agent_id?: string | null; + /** @description Filter by assigned agent. Optional. */ + assigned_agent_id?: string | null; + status?: string | null; + priority?: string | null; + created_by?: string | null; + limit?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TaskListResponse"]; + }; + }; + /** @description Task store not initialized */ + 503: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + create_task: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreateTaskRequest"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TaskResponse"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Task store not initialized */ + 503: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + get_task: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Task number */ + number: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TaskResponse"]; + }; + }; + /** @description Task not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Task store not initialized */ + 503: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + update_task: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Task number */ + number: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["UpdateTaskRequest"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TaskResponse"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Task not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Task store not initialized */ + 503: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + delete_task: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Task number */ + number: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TaskActionResponse"]; + }; + }; + /** @description Task not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Task store not initialized */ + 503: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + approve_task: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Task number */ + number: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ApproveRequest"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TaskResponse"]; + }; + }; + /** @description Task not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Task store not initialized */ + 503: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + assign_task: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Task number */ + number: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["AssignRequest"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TaskResponse"]; + }; + }; + /** @description Task not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Task store not initialized */ + 503: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + execute_task: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Task number */ + number: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ApproveRequest"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TaskResponse"]; + }; + }; + /** @description Task not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Task pending approval */ + 409: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Task store not initialized */ + 503: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + list_tools: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ToolsResponse"]; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + topology: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TopologyResponse"]; + }; + }; + }; + }; + update_apply: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + update_check: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["UpdateStatus"]; + }; + }; + }; + }; + update_check_now: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["UpdateStatus"]; + }; + }; + }; + }; + webchat_history: { + parameters: { + query: { + /** @description Agent ID */ + agent_id: string; + /** @description Session ID */ + session_id: string; + /** @description Maximum number of messages to return (default: 100, max: 200) */ + limit: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["WebChatHistoryMessage"][]; + }; + }; + /** @description Agent not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + webchat_send: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["WebChatSendRequest"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["WebChatSendResponse"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Messaging manager not available */ + 503: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + list_workers: { + parameters: { + query: { + /** @description Agent ID */ + agent_id: string; + /** @description Maximum number of results to return */ + limit: number; + /** @description Number of results to skip */ + offset: number; + /** @description Filter by worker status */ + status?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["WorkerListResponse"]; + }; + }; + /** @description Agent not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + worker_detail: { + parameters: { + query: { + /** @description Agent ID */ + agent_id: string; + /** @description Worker ID */ + worker_id: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["WorkerDetailResponse"]; + }; + }; + /** @description Agent or worker not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; +} diff --git a/interface/src/api/types.ts b/interface/src/api/types.ts new file mode 100644 index 000000000..116e8cc05 --- /dev/null +++ b/interface/src/api/types.ts @@ -0,0 +1,389 @@ +// Type re-exports from OpenAPI schema +import type { components } from "./schema"; + +// ============================================================================= +// System Types +// ============================================================================= + +export type StatusResponse = components["schemas"]["StatusResponse"]; +export type IdleResponse = components["schemas"]["IdleResponse"]; +export type HealthResponse = components["schemas"]["HealthResponse"]; +export type InstanceOverviewResponse = components["schemas"]["InstanceOverviewResponse"]; + +// ============================================================================= +// Event/SSE Types +// ============================================================================= + +// NOTE: These SSE event types are NOT in the OpenAPI schema - they are manual types +// defined in client.ts because they come from server-sent events, not REST responses. +// Keep these in client.ts: +// - InboundMessageEvent +// - OutboundMessageEvent +// - OutboundMessageDeltaEvent +// - TypingStateEvent +// - WorkerStartedEvent +// - WorkerStatusEvent +// - WorkerIdleEvent +// - WorkerCompletedEvent +// - BranchStartedEvent +// - BranchCompletedEvent +// - ToolStartedEvent +// - ToolCompletedEvent +// - OpenCodePartUpdatedEvent +// - WorkerTextEvent +// - CortexChatMessageEvent +// - ApiEvent (union of all above) +// - OpenCodeToolState +// - OpenCodePart +// - CortexChatSSEEvent + +// ============================================================================= +// Channel Types +// ============================================================================= + +export type ChannelResponse = components["schemas"]["ChannelResponse"]; +export type ChannelsResponse = components["schemas"]["ChannelsResponse"]; +export type MessagesResponse = components["schemas"]["MessagesResponse"]; +export type TimelineItem = components["schemas"]["TimelineItem"]; + +// Status-related +export type CancelProcessRequest = components["schemas"]["CancelProcessRequest"]; +export type CancelProcessResponse = components["schemas"]["CancelProcessResponse"]; + +// Prompt inspection +export type PromptCaptureBody = components["schemas"]["PromptCaptureBody"]; + +// Archive +export type SetChannelArchiveRequest = components["schemas"]["SetChannelArchiveRequest"]; + +// ============================================================================= +// Worker Types +// ============================================================================= + +export type WorkerListItem = components["schemas"]["WorkerListItem"]; +export type WorkerListResponse = components["schemas"]["WorkerListResponse"]; +export type WorkerDetailResponse = components["schemas"]["WorkerDetailResponse"]; + +// Transcript +export type ActionContent = components["schemas"]["ActionContent"]; +export type TranscriptStep = components["schemas"]["TranscriptStep"]; + +// ============================================================================= +// Agent Types +// ============================================================================= + +export type AgentInfo = components["schemas"]["AgentInfo"]; +export type AgentsResponse = components["schemas"]["AgentsResponse"]; +export type AgentSummary = components["schemas"]["AgentSummary"]; +export type AgentOverviewResponse = components["schemas"]["AgentOverviewResponse"]; +export type AgentProfile = components["schemas"]["AgentProfile"]; +export type AgentProfileResponse = components["schemas"]["AgentProfileResponse"]; + +// CRUD +export type CreateAgentRequest = components["schemas"]["CreateAgentRequest"]; +export type UpdateAgentRequest = components["schemas"]["UpdateAgentRequest"]; + +// Warmup +export type WarmupSection = components["schemas"]["WarmupSection"]; +export type WarmupUpdate = components["schemas"]["WarmupUpdate"]; +export type WarmupStatus = components["schemas"]["WarmupStatus"]; +export type WarmupStatusEntry = components["schemas"]["WarmupStatusEntry"]; +export type WarmupStatusResponse = components["schemas"]["WarmupStatusResponse"]; +export type WarmupState = components["schemas"]["WarmupState"]; +export type WarmupTriggerRequest = components["schemas"]["WarmupTriggerRequest"]; +export type WarmupTriggerResponse = components["schemas"]["WarmupTriggerResponse"]; + +// Activity +export type ActivityDayCount = components["schemas"]["ActivityDayCount"]; +export type DayCount = components["schemas"]["DayCount"]; +export type HeatmapCell = components["schemas"]["HeatmapCell"]; + +// ============================================================================= +// Memory Types +// ============================================================================= + +export type Memory = components["schemas"]["Memory"]; +export type MemoryType = components["schemas"]["MemoryType"]; +export type MemoriesListResponse = components["schemas"]["MemoriesListResponse"]; + +// Search +export type MemorySearchResult = components["schemas"]["MemorySearchResult"]; +export type MemoriesSearchResponse = components["schemas"]["MemoriesSearchResponse"]; + +// Graph +export type Association = components["schemas"]["Association"]; +export type RelationType = components["schemas"]["RelationType"]; +export type MemoryGraphResponse = components["schemas"]["MemoryGraphResponse"]; +export type MemoryGraphNeighborsResponse = components["schemas"]["MemoryGraphNeighborsResponse"]; + +// ============================================================================= +// Cortex Types +// ============================================================================= + +export type CortexEvent = components["schemas"]["CortexEvent"]; +export type CortexEventsResponse = components["schemas"]["CortexEventsResponse"]; + +// Chat +export type CortexChatMessage = components["schemas"]["CortexChatMessage"]; +export type CortexChatMessagesResponse = components["schemas"]["CortexChatMessagesResponse"]; +export type CortexChatThread = components["schemas"]["CortexChatThread"]; +export type CortexChatThreadsResponse = components["schemas"]["CortexChatThreadsResponse"]; +export type CortexChatToolCall = components["schemas"]["CortexChatToolCall"]; +export type CortexChatSendRequest = components["schemas"]["CortexChatSendRequest"]; +export type CortexChatDeleteThreadRequest = components["schemas"]["CortexChatDeleteThreadRequest"]; + +// ============================================================================= +// Config Types +// ============================================================================= + +export type AgentConfigResponse = components["schemas"]["AgentConfigResponse"]; +export type AgentConfigUpdateRequest = components["schemas"]["AgentConfigUpdateRequest"]; + +// Sections +export type RoutingSection = components["schemas"]["RoutingSection"]; +export type TuningSection = components["schemas"]["TuningSection"]; +export type CompactionSection = components["schemas"]["CompactionSection"]; +export type CortexSection = components["schemas"]["CortexSection"]; +export type CoalesceSection = components["schemas"]["CoalesceSection"]; +export type MemoryPersistenceSection = components["schemas"]["MemoryPersistenceSection"]; +export type BrowserSection = components["schemas"]["BrowserSection"]; +export type ChannelSection = components["schemas"]["ChannelSection"]; +export type SandboxSection = components["schemas"]["SandboxSection"]; +export type ProjectsSection = components["schemas"]["ProjectsSection"]; +export type DiscordSection = components["schemas"]["DiscordSection"]; + +// Updates (partial) +export type RoutingUpdate = components["schemas"]["RoutingUpdate"]; +export type TuningUpdate = components["schemas"]["TuningUpdate"]; +export type CompactionUpdate = components["schemas"]["CompactionUpdate"]; +export type CortexUpdate = components["schemas"]["CortexUpdate"]; +export type CoalesceUpdate = components["schemas"]["CoalesceUpdate"]; +export type MemoryPersistenceUpdate = components["schemas"]["MemoryPersistenceUpdate"]; +export type BrowserUpdate = components["schemas"]["BrowserUpdate"]; +export type ChannelUpdate = components["schemas"]["ChannelUpdate"]; +export type SandboxUpdate = components["schemas"]["SandboxUpdate"]; +export type ProjectsUpdate = components["schemas"]["ProjectsUpdate"]; +export type DiscordUpdate = components["schemas"]["DiscordUpdate"]; + +// Browser +export type ClosePolicy = components["schemas"]["ClosePolicy"]; + +// Global settings +export type GlobalSettingsResponse = components["schemas"]["GlobalSettingsResponse"]; +export type GlobalSettingsUpdate = components["schemas"]["GlobalSettingsUpdate"]; +export type GlobalSettingsUpdateResponse = components["schemas"]["GlobalSettingsUpdateResponse"]; + +// OpenCode (within global settings) +export type OpenCodeSettingsResponse = components["schemas"]["OpenCodeSettingsResponse"]; +export type OpenCodeSettingsUpdate = components["schemas"]["OpenCodeSettingsUpdate"]; +export type OpenCodePermissionsResponse = components["schemas"]["OpenCodePermissionsResponse"]; +export type OpenCodePermissionsUpdate = components["schemas"]["OpenCodePermissionsUpdate"]; + +// Raw config +export type RawConfigResponse = components["schemas"]["RawConfigResponse"]; +export type RawConfigUpdateRequest = components["schemas"]["RawConfigUpdateRequest"]; +export type RawConfigUpdateResponse = components["schemas"]["RawConfigUpdateResponse"]; + +// ============================================================================= +// Cron Types +// ============================================================================= + +export type CronJobInfo = components["schemas"]["CronJobInfo"]; +export type CronJobWithStats = components["schemas"]["CronJobWithStats"]; +export type CronListResponse = components["schemas"]["CronListResponse"]; +export type CronExecutionEntry = components["schemas"]["CronExecutionEntry"]; +export type CronExecutionsResponse = components["schemas"]["CronExecutionsResponse"]; +export type CronActionResponse = components["schemas"]["CronActionResponse"]; + +// Requests +export type CreateCronRequest = components["schemas"]["CreateCronRequest"]; +export type ToggleCronRequest = components["schemas"]["ToggleCronRequest"]; +export type TriggerCronRequest = components["schemas"]["TriggerCronRequest"]; + +// ============================================================================= +// Provider/Model Types +// ============================================================================= + +export type ProviderStatus = components["schemas"]["ProviderStatus"]; +export type ProvidersResponse = components["schemas"]["ProvidersResponse"]; +export type ProviderUpdateRequest = components["schemas"]["ProviderUpdateRequest"]; +export type ProviderUpdateResponse = components["schemas"]["ProviderUpdateResponse"]; + +// Model testing +export type ProviderModelTestRequest = components["schemas"]["ProviderModelTestRequest"]; +export type ProviderModelTestResponse = components["schemas"]["ProviderModelTestResponse"]; + +// OAuth +export type OpenAiOAuthBrowserStartRequest = components["schemas"]["OpenAiOAuthBrowserStartRequest"]; +export type OpenAiOAuthBrowserStartResponse = components["schemas"]["OpenAiOAuthBrowserStartResponse"]; +export type OpenAiOAuthBrowserStatusResponse = components["schemas"]["OpenAiOAuthBrowserStatusResponse"]; + +// Models +export type ModelInfo = components["schemas"]["ModelInfo"]; +export type ModelsResponse = components["schemas"]["ModelsResponse"]; + +// ============================================================================= +// Ingest Types +// ============================================================================= + +export type IngestFileInfo = components["schemas"]["IngestFileInfo"]; +export type IngestFilesResponse = components["schemas"]["IngestFilesResponse"]; +export type IngestUploadResponse = components["schemas"]["IngestUploadResponse"]; +export type IngestDeleteResponse = components["schemas"]["IngestDeleteResponse"]; + +// ============================================================================= +// Skills Types +// ============================================================================= + +export type SkillInfo = components["schemas"]["SkillInfo"]; +export type SkillsListResponse = components["schemas"]["SkillsListResponse"]; +export type SkillContentResponse = components["schemas"]["SkillContentResponse"]; + +// Install/Remove +export type InstallSkillRequest = components["schemas"]["InstallSkillRequest"]; +export type InstallSkillResponse = components["schemas"]["InstallSkillResponse"]; +export type RemoveSkillRequest = components["schemas"]["RemoveSkillRequest"]; +export type RemoveSkillResponse = components["schemas"]["RemoveSkillResponse"]; +export type UploadSkillResponse = components["schemas"]["UploadSkillResponse"]; + +// Registry +export type RegistrySkill = components["schemas"]["RegistrySkill"]; +export type RegistryBrowseResponse = components["schemas"]["RegistryBrowseResponse"]; +export type RegistrySearchResponse = components["schemas"]["RegistrySearchResponse"]; +export type RegistrySkillContentResponse = components["schemas"]["RegistrySkillContentResponse"]; + +// ============================================================================= +// Tasks Types +// ============================================================================= + +export type Task = components["schemas"]["Task"]; +export type TaskStatus = components["schemas"]["TaskStatus"]; +export type TaskPriority = components["schemas"]["TaskPriority"]; +export type TaskSubtask = components["schemas"]["TaskSubtask"]; +export type TaskListResponse = components["schemas"]["TaskListResponse"]; +export type TaskResponse = components["schemas"]["TaskResponse"]; +export type TaskActionResponse = components["schemas"]["TaskActionResponse"]; + +// Requests +export type CreateTaskRequest = components["schemas"]["CreateTaskRequest"]; +export type UpdateTaskRequest = components["schemas"]["UpdateTaskRequest"]; +export type ApproveRequest = components["schemas"]["ApproveRequest"]; +export type AssignRequest = components["schemas"]["AssignRequest"]; + +// ============================================================================= +// Messaging Types +// ============================================================================= + +export type PlatformStatus = components["schemas"]["PlatformStatus"]; +export type AdapterInstanceStatus = components["schemas"]["AdapterInstanceStatus"]; +export type MessagingStatusResponse = components["schemas"]["MessagingStatusResponse"]; +export type MessagingInstanceActionResponse = components["schemas"]["MessagingInstanceActionResponse"]; + +// Instances +export type CreateMessagingInstanceRequest = components["schemas"]["CreateMessagingInstanceRequest"]; +export type DeleteMessagingInstanceRequest = components["schemas"]["DeleteMessagingInstanceRequest"]; +export type InstanceCredentials = components["schemas"]["InstanceCredentials"]; + +// Bindings +export type BindingResponse = components["schemas"]["BindingResponse"]; +export type BindingsListResponse = components["schemas"]["BindingsListResponse"]; +export type CreateBindingRequest = components["schemas"]["CreateBindingRequest"]; +export type CreateBindingResponse = components["schemas"]["CreateBindingResponse"]; +export type UpdateBindingRequest = components["schemas"]["UpdateBindingRequest"]; +export type UpdateBindingResponse = components["schemas"]["UpdateBindingResponse"]; +export type DeleteBindingRequest = components["schemas"]["DeleteBindingRequest"]; +export type DeleteBindingResponse = components["schemas"]["DeleteBindingResponse"]; +export type PlatformCredentials = components["schemas"]["PlatformCredentials"]; + +// Toggles +export type TogglePlatformRequest = components["schemas"]["TogglePlatformRequest"]; +export type DisconnectPlatformRequest = components["schemas"]["DisconnectPlatformRequest"]; + +// Web chat +export type WebChatSendRequest = components["schemas"]["WebChatSendRequest"]; +export type WebChatSendResponse = components["schemas"]["WebChatSendResponse"]; +export type WebChatHistoryMessage = components["schemas"]["WebChatHistoryMessage"]; + +// ============================================================================= +// Links Types +// ============================================================================= + +export type TopologyAgent = components["schemas"]["TopologyAgent"]; +export type TopologyLink = components["schemas"]["TopologyLink"]; +export type TopologyGroup = components["schemas"]["TopologyGroup"]; +export type TopologyHuman = components["schemas"]["TopologyHuman"]; +export type TopologyResponse = components["schemas"]["TopologyResponse"]; + +// Groups +export type CreateGroupRequest = components["schemas"]["CreateGroupRequest"]; +export type UpdateGroupRequest = components["schemas"]["UpdateGroupRequest"]; + +// Humans +export type CreateHumanRequest = components["schemas"]["CreateHumanRequest"]; +export type UpdateHumanRequest = components["schemas"]["UpdateHumanRequest"]; + +// Links +export type CreateLinkRequest = components["schemas"]["CreateLinkRequest"]; +export type UpdateLinkRequest = components["schemas"]["UpdateLinkRequest"]; + +// ============================================================================= +// Projects Types +// ============================================================================= + +export type Project = components["schemas"]["Project"]; +export type ProjectStatus = components["schemas"]["ProjectStatus"]; +export type ProjectRepo = components["schemas"]["ProjectRepo"]; +export type ProjectWorktree = components["schemas"]["ProjectWorktree"]; +export type ProjectWorktreeWithRepo = components["schemas"]["ProjectWorktreeWithRepo"]; +export type ProjectWithRelations = components["schemas"]["ProjectWithRelations"]; +export type ProjectListResponse = components["schemas"]["ProjectListResponse"]; +export type ProjectResponse = components["schemas"]["ProjectResponse"]; + +// Disk usage +export type DiskUsageEntry = components["schemas"]["DiskUsageEntry"]; +export type DiskUsageResponse = components["schemas"]["DiskUsageResponse"]; + +// CRUD +export type CreateProjectRequest = components["schemas"]["CreateProjectRequest"]; +export type UpdateProjectRequest = components["schemas"]["UpdateProjectRequest"]; +export type CreateRepoRequest = components["schemas"]["CreateRepoRequest"]; +export type CreateWorktreeRequest = components["schemas"]["CreateWorktreeRequest"]; + +// Responses +export type RepoResponse = components["schemas"]["RepoResponse"]; +export type WorktreeResponse = components["schemas"]["WorktreeResponse"]; +export type ActionResponse = components["schemas"]["ActionResponse"]; + +// ============================================================================= +// Secrets/Settings Types +// ============================================================================= + +export type SecretCategory = components["schemas"]["SecretCategory"]; +export type SecretListItem = components["schemas"]["SecretListItem"]; +export type SecretListResponse = components["schemas"]["SecretListResponse"]; +export type SecretInfoResponse = components["schemas"]["SecretInfoResponse"]; + +// CRUD +export type PutSecretBody = components["schemas"]["PutSecretBody"]; +export type PutSecretResponse = components["schemas"]["PutSecretResponse"]; +export type DeleteSecretResponse = components["schemas"]["DeleteSecretResponse"]; + +// Encryption +export type EncryptResponse = components["schemas"]["EncryptResponse"]; +export type UnlockBody = components["schemas"]["UnlockBody"]; + +// Migration +export type MigrationItem = components["schemas"]["MigrationItem"]; +export type MigrateResponse = components["schemas"]["MigrateResponse"]; + +// Export/Import +export type ExportData = components["schemas"]["ExportData"]; +export type ExportEntry = components["schemas"]["ExportEntry"]; +export type ImportBody = components["schemas"]["ImportBody"]; + +// Storage +export type StorageStatus = components["schemas"]["StorageStatus"]; + +// NOTE: Backup operations use raw binary data (zip/octet-stream), not JSON schema types: +// - Backup export returns application/zip +// - Backup restore accepts application/octet-stream diff --git a/interface/src/components/ProfileAvatar.tsx b/interface/src/components/ProfileAvatar.tsx index 3a90ac377..10b1f81d1 100644 --- a/interface/src/components/ProfileAvatar.tsx +++ b/interface/src/components/ProfileAvatar.tsx @@ -23,9 +23,9 @@ interface ProfileAvatarProps { /** Extra CSS classes on the outer element. */ className?: string; /** Custom gradient start color (overrides seed gradient). */ - gradientStart?: string; + gradientStart?: string | null | undefined; /** Custom gradient end color (overrides seed gradient). */ - gradientEnd?: string; + gradientEnd?: string | null | undefined; /** URL to a custom avatar image (takes precedence over gradient). */ avatarUrl?: string; } diff --git a/interface/src/components/Sidebar.tsx b/interface/src/components/Sidebar.tsx index a726f03a3..9c76ae9ca 100644 --- a/interface/src/components/Sidebar.tsx +++ b/interface/src/components/Sidebar.tsx @@ -32,9 +32,9 @@ interface SidebarProps { interface SortableAgentItemProps { agentId: string; - displayName?: string; - gradientStart?: string; - gradientEnd?: string; + displayName?: string | null | undefined; + gradientStart?: string | null | undefined; + gradientEnd?: string | null | undefined; isActive: boolean; } @@ -98,12 +98,12 @@ export function Sidebar({ liveStates: _liveStates }: SidebarProps) { const agentIds = useMemo(() => agents.map((a) => a.id), [agents]); const agentDisplayNames = useMemo(() => { - const map: Record = {}; + const map: Record = {}; for (const a of agents) map[a.id] = a.display_name; return map; }, [agents]); const agentGradients = useMemo(() => { - const map: Record = {}; + const map: Record = {}; for (const a of agents) map[a.id] = { start: a.gradient_start, end: a.gradient_end }; return map; }, [agents]); diff --git a/interface/src/components/TaskBoard.tsx b/interface/src/components/TaskBoard.tsx index bc9c82792..b98acf34a 100644 --- a/interface/src/components/TaskBoard.tsx +++ b/interface/src/components/TaskBoard.tsx @@ -193,7 +193,7 @@ export interface TaskBoardProps { /** Agent ID used as owner when creating tasks. Required for create to work. */ ownerAgentId?: string; /** Optional agent name resolver for showing agent badges on cards. */ - agentNames?: Record; + agentNames?: Record; /** Whether to show agent badges on task cards (useful in global view). */ showAgentBadge?: boolean; } @@ -418,7 +418,7 @@ function KanbanColumn({ label: string; tasks: TaskItem[]; showAgentBadge?: boolean; - agentNames?: Record; + agentNames?: Record; onSelect: (task: TaskItem) => void; onApprove: (task: TaskItem) => void; onExecute: (task: TaskItem) => void; @@ -475,7 +475,7 @@ function TaskCard({ }: { task: TaskItem; showAgentBadge?: boolean; - agentNames?: Record; + agentNames?: Record; onSelect: () => void; onApprove: () => void; onExecute: () => void; @@ -701,7 +701,7 @@ function TaskDetailDialog({ onStatusChange, }: { task: TaskItem; - agentNames?: Record; + agentNames?: Record; showAgentBadge?: boolean; onClose: () => void; onApprove: () => void; diff --git a/interface/src/components/TopologyGraph.tsx b/interface/src/components/TopologyGraph.tsx index 117b4c3e5..c04e0f5e8 100644 --- a/interface/src/components/TopologyGraph.tsx +++ b/interface/src/components/TopologyGraph.tsx @@ -30,6 +30,8 @@ import { type AgentSummary, type TopologyResponse, type TopologyGroup, + type TopologyAgent, + type TopologyHuman, type LinkDirection, type LinkKind, } from "@/api/client"; @@ -529,17 +531,7 @@ function EdgeConfigPanel({ // -- Human Edit Dialog -- interface HumanEditDialogProps { - human: { - id: string; - display_name?: string; - role?: string; - bio?: string; - description?: string; - discord_id?: string; - telegram_id?: string; - slack_id?: string; - email?: string; - } | null; + human: TopologyHuman | null; open: boolean; onOpenChange: (open: boolean) => void; onUpdate: (fields: { @@ -757,7 +749,7 @@ function HumanEditDialog({ // -- Agent Edit Dialog -- interface AgentEditDialogProps { - agent: { id: string; display_name?: string; role?: string } | null; + agent: TopologyAgent | null; open: boolean; onOpenChange: (open: boolean) => void; onUpdate: (displayName: string, role: string) => void; @@ -936,9 +928,9 @@ function TopologyGraphInner({ activeEdges, agents }: TopologyGraphInnerProps) { const queryClient = useQueryClient(); const [selectedEdge, setSelectedEdge] = useState(null); const [selectedGroup, setSelectedGroup] = useState(null); - const [selectedHuman, setSelectedHuman] = useState<{ id: string; display_name?: string; role?: string; bio?: string } | null>(null); + const [selectedHuman, setSelectedHuman] = useState(null); const [humanDialogOpen, setHumanDialogOpen] = useState(false); - const [selectedAgent, setSelectedAgent] = useState<{ id: string; display_name?: string; role?: string } | null>(null); + const [selectedAgent, setSelectedAgent] = useState(null); const [agentDialogOpen, setAgentDialogOpen] = useState(false); const { data, isLoading, error } = useQuery({ @@ -969,7 +961,7 @@ function TopologyGraphInner({ activeEdges, agents }: TopologyGraphInnerProps) { // Build agent info lookup (for gradient colors) const agentInfoMap = useMemo(() => { - const map = new Map(); + const map = new Map(); for (const info of agentInfoData?.agents ?? []) { map.set(info.id, { gradient_start: info.gradient_start, gradient_end: info.gradient_end }); } @@ -1280,11 +1272,7 @@ function TopologyGraphInner({ activeEdges, agents }: TopologyGraphInnerProps) { (agentId: string) => { const topoAgent = data?.agents.find((a) => a.id === agentId); if (topoAgent) { - setSelectedAgent({ - id: topoAgent.id, - display_name: topoAgent.display_name, - role: topoAgent.role, - }); + setSelectedAgent(topoAgent); setAgentDialogOpen(true); setSelectedEdge(null); setSelectedGroup(null); @@ -1627,7 +1615,7 @@ function buildGraph( data: TopologyResponse, activeEdges: Set, agentProfiles: Map, - agentInfoMap: Map, + agentInfoMap: Map, ): { initialNodes: Node[]; initialEdges: Edge[] } { const saved = loadPositions(); const savedHandles = loadHandles(); diff --git a/interface/src/hooks/useAudioRecorder.ts b/interface/src/hooks/useAudioRecorder.ts new file mode 100644 index 000000000..686212d7f --- /dev/null +++ b/interface/src/hooks/useAudioRecorder.ts @@ -0,0 +1,35 @@ +// Stub implementations for voice overlay hooks +// TODO: Implement actual audio recording and TTS playback + +export type AudioRecorderStatus = "idle" | "recording" | "processing"; + +export function useAudioRecorder() { + return { + state: "idle" as AudioRecorderStatus, + startRecording: () => Promise.resolve(), + stopRecording: () => Promise.resolve(new Blob()), + audioLevel: 0, + spectrumLevels: [] as number[], + }; +} + +export interface TtsProfile { + id: string; + name: string; +} + +export type TtsStatus = "idle" | "playing"; + +export function useTtsPlayback(_agentId?: string, _profileId?: string) { + return { + speak: (_text: string, _onDone?: () => void) => Promise.resolve(), + stop: () => {}, + playbackLevel: 0, + spectrumLevels: [] as number[], + status: "idle" as TtsStatus, + }; +} + +export async function fetchTtsProfiles(_agentId: string): Promise { + return []; +} diff --git a/interface/src/hooks/useTtsPlayback.ts b/interface/src/hooks/useTtsPlayback.ts new file mode 100644 index 000000000..b709691a8 --- /dev/null +++ b/interface/src/hooks/useTtsPlayback.ts @@ -0,0 +1,23 @@ +// Stub implementation for TTS playback hook +// TODO: Implement actual TTS playback + +export interface TtsProfile { + id: string; + name: string; +} + +export type TtsStatus = "idle" | "playing"; + +export function useTtsPlayback(_agentId?: string, _profileId?: string) { + return { + speak: (_text: string, _agentId?: string, _profileId?: string) => Promise.resolve(), + stop: () => {}, + playbackLevel: 0, + spectrumLevels: [] as number[], + status: "idle" as TtsStatus, + }; +} + +export async function fetchTtsProfiles(_agentId: string): Promise { + return []; +} diff --git a/interface/src/routes/AgentDetail.tsx b/interface/src/routes/AgentDetail.tsx index dc5bb804e..de31af6ff 100644 --- a/interface/src/routes/AgentDetail.tsx +++ b/interface/src/routes/AgentDetail.tsx @@ -209,13 +209,13 @@ export function AgentDetail({ agentId, liveStates }: AgentDetailProps) { )} {/* Recent Cortex Events */} - {overviewData && overviewData.recent_cortex_events.length > 0 && ( - - )} + {overviewData && overviewData.recent_cortex_events.length > 0 && ( + + )} ); @@ -233,7 +233,7 @@ function HeroSection({ onDelete, }: { agentId: string; - displayName?: string; + displayName?: string | null | undefined; channelCount: number; workers: number; branches: number; @@ -718,9 +718,9 @@ function CronSection({ agentId, jobs }: { agentId: string; jobs: CronJobInfo[] } {job.prompt} - - {formatCronSchedule(job.cron_expr, job.interval_secs)} - + + {formatCronSchedule(job.cron_expr ?? null, job.interval_secs)} + {job.active_hours && ( {String(job.active_hours[0]).padStart(2, "0")}:00-{String(job.active_hours[1]).padStart(2, "0")}:00 diff --git a/interface/src/routes/AgentWorkers.tsx b/interface/src/routes/AgentWorkers.tsx index a7a908692..5c4be8b01 100644 --- a/interface/src/routes/AgentWorkers.tsx +++ b/interface/src/routes/AgentWorkers.tsx @@ -371,7 +371,7 @@ function WorkerDetail({ }) { const isLive = detail.status === "running" || !!liveWorker; const isIdle = liveWorker?.isIdle ?? detail.status === "idle"; - const duration = durationBetween(detail.started_at, detail.completed_at); + const duration = durationBetween(detail.started_at, detail.completed_at ?? null); const displayStatus = liveWorker?.status; const currentTool = liveWorker?.currentTool; const toolCalls = liveWorker?.toolCalls ?? detail.tool_calls ?? 0; @@ -443,13 +443,13 @@ function WorkerDetail({
{detail.channel_name && {detail.channel_name}} - {hasOpenCodeEmbed && detail.opencode_port && ( - - )} + {hasOpenCodeEmbed && detail.opencode_port && ( + + )} {isRunning ? ( Running for{" "} @@ -512,12 +512,12 @@ function WorkerDetail({
{/* Content */} - {activeTab === "opencode" && hasOpenCodeEmbed ? ( - + {activeTab === "opencode" && hasOpenCodeEmbed ? ( + ) : (
{/* Result section */} diff --git a/interface/src/routes/ChannelDetail.tsx b/interface/src/routes/ChannelDetail.tsx index 6f520531a..84d293261 100644 --- a/interface/src/routes/ChannelDetail.tsx +++ b/interface/src/routes/ChannelDetail.tsx @@ -271,13 +271,13 @@ function TimelineEntry({ item, liveWorkers, liveBranches, channelId, agentId }: ); case "branch_run": { const live = liveBranches[item.id]; - if (live) return ; - return ; + if (live) return ; + return ; } case "worker_run": { const live = liveWorkers[item.id]; - if (live) return ; - return ; + if (live) return ; + return ; } } } diff --git a/interface/src/routes/GlobalTasks.tsx b/interface/src/routes/GlobalTasks.tsx index 2027e7dc9..386546747 100644 --- a/interface/src/routes/GlobalTasks.tsx +++ b/interface/src/routes/GlobalTasks.tsx @@ -18,7 +18,7 @@ export function GlobalTasks() { const effectiveOwner = selectedOwnerId ?? agents[0]?.id; const agentNames = useMemo(() => { - const map: Record = {}; + const map: Record = {}; for (const agent of agents) { map[agent.id] = agent.display_name; } diff --git a/interface/src/routes/Orchestrate.tsx b/interface/src/routes/Orchestrate.tsx index f0be815d9..7579bfd62 100644 --- a/interface/src/routes/Orchestrate.tsx +++ b/interface/src/routes/Orchestrate.tsx @@ -354,7 +354,7 @@ function WorkerColumn({worker}: {worker: OrchestrationWorker}) {
diff --git a/justfile b/justfile index 3190a8a9f..5c26447e2 100644 --- a/justfile +++ b/justfile @@ -27,6 +27,15 @@ test-integration-compile: gate-pr: preflight ./scripts/gate-pr.sh +typegen: + cargo run --bin openapi-spec > /tmp/spacebot-openapi.json + cd interface && bunx openapi-typescript /tmp/spacebot-openapi.json -o src/api/schema.d.ts + +check-typegen: + cargo run --bin openapi-spec > /tmp/spacebot-openapi-check.json + cd interface && bunx openapi-typescript /tmp/spacebot-openapi-check.json -o /tmp/spacebot-schema-check.d.ts + diff interface/src/api/schema.d.ts /tmp/spacebot-schema-check.d.ts + gate-pr-ci: preflight-ci ./scripts/gate-pr.sh --ci diff --git a/src/agent/cortex.rs b/src/agent/cortex.rs index 0f52ac72f..c532f49d7 100644 --- a/src/agent/cortex.rs +++ b/src/agent/cortex.rs @@ -758,7 +758,7 @@ pub enum Signal { } /// A persisted cortex action record. -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, Serialize, utoipa::ToSchema)] pub struct CortexEvent { pub id: String, pub event_type: String, @@ -3011,7 +3011,7 @@ pub async fn maybe_synthesize_daily_summary( // -- Agent Profile -- /// Persisted agent profile generated by the cortex. -#[derive(Debug, Clone, Serialize, serde::Deserialize)] +#[derive(Debug, Clone, Serialize, serde::Deserialize, utoipa::ToSchema)] pub struct AgentProfile { pub agent_id: String, pub display_name: Option, diff --git a/src/agent/cortex_chat.rs b/src/agent/cortex_chat.rs index e95338bdf..c1118048f 100644 --- a/src/agent/cortex_chat.rs +++ b/src/agent/cortex_chat.rs @@ -23,7 +23,7 @@ use std::time::Duration; use tokio::sync::{Mutex, RwLock}; /// A persisted cortex chat message. -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, Serialize, utoipa::ToSchema)] pub struct CortexChatMessage { pub id: String, pub thread_id: String, @@ -37,7 +37,7 @@ pub struct CortexChatMessage { } /// Summary of a cortex chat thread (returned by list_threads). -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, Serialize, utoipa::ToSchema)] pub struct CortexChatThread { pub thread_id: String, pub preview: String, @@ -47,7 +47,7 @@ pub struct CortexChatThread { } /// A tool call + result pair persisted alongside assistant messages. -#[derive(Debug, Clone, Serialize, serde::Deserialize)] +#[derive(Debug, Clone, Serialize, serde::Deserialize, utoipa::ToSchema)] pub struct CortexChatToolCall { pub id: String, pub tool: String, diff --git a/src/api.rs b/src/api.rs index 0bd99aec9..bb6231503 100644 --- a/src/api.rs +++ b/src/api.rs @@ -32,5 +32,5 @@ mod tools; mod webchat; mod workers; -pub use server::start_http_server; +pub use server::{api_router, start_http_server}; pub use state::{AgentInfo, ApiEvent, ApiState}; diff --git a/src/api/agents.rs b/src/api/agents.rs index 593ac54bf..bf077920a 100644 --- a/src/api/agents.rs +++ b/src/api/agents.rs @@ -23,12 +23,12 @@ fn hosted_agent_limit() -> Option { .filter(|value| *value > 0) } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct AgentsResponse { agents: Vec, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct AgentOverviewResponse { memory_counts: HashMap, memory_total: i64, @@ -42,27 +42,27 @@ pub(super) struct AgentOverviewResponse { latest_bulletin: Option, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] struct DayCount { date: String, count: i64, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] struct ActivityDayCount { date: String, branches: i64, workers: i64, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] struct HeatmapCell { day: i64, hour: i64, count: i64, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] struct CronJobInfo { id: String, prompt: String, @@ -75,7 +75,7 @@ struct CronJobInfo { timeout_secs: Option, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct InstanceOverviewResponse { version: &'static str, uptime_seconds: u64, @@ -83,7 +83,7 @@ pub(super) struct InstanceOverviewResponse { agents: Vec, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] struct AgentSummary { id: String, channel_count: usize, @@ -95,24 +95,24 @@ struct AgentSummary { profile: Option, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct AgentProfileResponse { profile: Option, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct IdentityResponse { soul: Option, identity: Option, role: Option, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct IdentityQuery { agent_id: String, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct IdentityUpdateRequest { agent_id: String, soul: Option, @@ -120,12 +120,12 @@ pub(super) struct IdentityUpdateRequest { role: Option, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct AgentOverviewQuery { agent_id: String, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub struct CreateAgentRequest { pub agent_id: String, pub display_name: Option, @@ -139,7 +139,7 @@ pub struct CreateAgentResult { pub message: String, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct UpdateAgentRequest { agent_id: String, display_name: Option, @@ -148,51 +148,51 @@ pub(super) struct UpdateAgentRequest { gradient_end: Option, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct DeleteAgentQuery { agent_id: String, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct AgentMcpQuery { agent_id: String, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct ReconnectMcpRequest { agent_id: String, server_name: String, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct AgentMcpResponse { servers: Vec, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct WarmupQuery { agent_id: Option, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct WarmupTriggerRequest { agent_id: Option, #[serde(default)] force: bool, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct WarmupStatusEntry { agent_id: String, status: crate::config::WarmupStatus, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct WarmupStatusResponse { statuses: Vec, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct WarmupTriggerResponse { status: &'static str, forced: bool, @@ -272,6 +272,14 @@ fn resolve_warmup_agent_ids( } /// List all configured agents with their config summaries. +#[utoipa::path( + get, + path = "/agents", + responses( + (status = 200, body = AgentsResponse), + ), + tag = "agents", +)] pub(super) async fn list_agents(State(state): State>) -> Json { let agents = state.agent_configs.load(); Json(AgentsResponse { @@ -280,6 +288,18 @@ pub(super) async fn list_agents(State(state): State>) -> Json>, Query(query): Query, @@ -294,6 +314,17 @@ pub(super) async fn list_agent_mcp( } /// Force reconnect for a single MCP server on an agent. +#[utoipa::path( + post, + path = "/agents/mcp/reconnect", + request_body = ReconnectMcpRequest, + responses( + (status = 200, body = serde_json::Value), + (status = 404, description = "Agent not found"), + (status = 400, description = "Failed to reconnect"), + ), + tag = "agents", +)] pub(super) async fn reconnect_agent_mcp( State(state): State>, Json(request): Json, @@ -325,6 +356,18 @@ pub(super) async fn reconnect_agent_mcp( } /// Get warmup status for one agent or all agents. +#[utoipa::path( + get, + path = "/agents/warmup", + params( + ("agent_id" = Option, Query, description = "Optional agent ID to get status for a specific agent"), + ), + responses( + (status = 200, body = WarmupStatusResponse), + (status = 404, description = "Agent not found"), + ), + tag = "agents", +)] pub(super) async fn get_warmup_status( State(state): State>, Query(query): Query, @@ -354,6 +397,17 @@ pub(super) async fn get_warmup_status( } /// Trigger warmup for one agent or all agents. +#[utoipa::path( + post, + path = "/agents/warmup/trigger", + request_body = WarmupTriggerRequest, + responses( + (status = 200, body = WarmupTriggerResponse), + (status = 503, description = "LLM manager not available"), + (status = 404, description = "Agent not found"), + ), + tag = "agents", +)] pub(super) async fn trigger_warmup( State(state): State>, Json(request): Json, @@ -462,6 +516,18 @@ pub(super) async fn trigger_warmup( } /// Create a new agent and initialize it live (directories, databases, memory, identity, cron, cortex). +#[utoipa::path( + post, + path = "/agents", + request_body = CreateAgentRequest, + responses( + (status = 201, body = serde_json::Value, description = "Agent created successfully"), + (status = 400, description = "Invalid request or agent limit reached"), + (status = 409, description = "Agent already exists"), + (status = 500, description = "Internal server error"), + ), + tag = "agents", +)] pub(super) async fn create_agent( State(state): State>, Json(request): Json, @@ -995,7 +1061,7 @@ pub async fn create_agent_internal( role: agent_config.role.clone(), gradient_start: agent_config.gradient_start.clone(), gradient_end: agent_config.gradient_end.clone(), - workspace: agent_config.workspace.clone(), + workspace: agent_config.workspace.to_string_lossy().to_string(), context_window: agent_config.context_window, max_turns: agent_config.max_turns, max_concurrent_branches: agent_config.max_concurrent_branches, @@ -1032,6 +1098,18 @@ pub async fn create_agent_internal( } /// Update an agent's display_name and role in config.toml. +#[utoipa::path( + put, + path = "/agents", + request_body = UpdateAgentRequest, + responses( + (status = 200, body = serde_json::Value), + (status = 404, description = "Agent not found"), + (status = 400, description = "Invalid request"), + (status = 500, description = "Internal server error"), + ), + tag = "agents", +)] pub(super) async fn update_agent( State(state): State>, Json(request): Json, @@ -1157,6 +1235,19 @@ pub(super) async fn update_agent( } /// Delete an agent: remove from config.toml, clean up API state, signal main loop. +#[utoipa::path( + delete, + path = "/agents", + params( + ("agent_id" = String, Query, description = "Agent ID to delete"), + ), + responses( + (status = 200, body = serde_json::Value), + (status = 400, description = "Invalid request"), + (status = 500, description = "Internal server error"), + ), + tag = "agents", +)] pub(super) async fn delete_agent( State(state): State>, Query(query): Query, @@ -1313,6 +1404,19 @@ pub(super) async fn delete_agent( } /// Get overview stats for an agent: memory breakdown, channels, cron, cortex. +#[utoipa::path( + get, + path = "/agents/overview", + params( + ("agent_id" = String, Query, description = "Agent ID"), + ), + responses( + (status = 200, body = AgentOverviewResponse), + (status = 404, description = "Agent not found"), + (status = 500, description = "Internal server error"), + ), + tag = "agents", +)] pub(super) async fn agent_overview( State(state): State>, Query(query): Query, @@ -1488,6 +1592,14 @@ pub(super) async fn agent_overview( } /// Get instance-wide overview for the main dashboard. +#[utoipa::path( + get, + path = "/agents/instance", + responses( + (status = 200, body = InstanceOverviewResponse), + ), + tag = "agents", +)] pub(super) async fn instance_overview( State(state): State>, ) -> Result, StatusCode> { @@ -1589,6 +1701,18 @@ pub(super) async fn instance_overview( } /// Get the cortex-generated profile for an agent. +#[utoipa::path( + get, + path = "/agents/profile", + params( + ("agent_id" = String, Query, description = "Agent ID"), + ), + responses( + (status = 200, body = AgentProfileResponse), + (status = 404, description = "Agent not found"), + ), + tag = "agents", +)] pub(super) async fn get_agent_profile( State(state): State>, Query(query): Query, @@ -1602,6 +1726,18 @@ pub(super) async fn get_agent_profile( } /// Get identity files (SOUL.md, IDENTITY.md, ROLE.md) for an agent. +#[utoipa::path( + get, + path = "/agents/identity", + params( + ("agent_id" = String, Query, description = "Agent ID"), + ), + responses( + (status = 200, body = IdentityResponse), + (status = 404, description = "Agent not found"), + ), + tag = "agents", +)] pub(super) async fn get_identity( State(state): State>, Query(query): Query, @@ -1622,6 +1758,17 @@ pub(super) async fn get_identity( /// Update identity files for an agent. Only writes files for fields that are present. /// The file watcher will pick up changes and hot-reload identity into RuntimeConfig. +#[utoipa::path( + put, + path = "/agents/identity", + request_body = IdentityUpdateRequest, + responses( + (status = 200, body = IdentityResponse), + (status = 404, description = "Agent not found"), + (status = 500, description = "Internal server error"), + ), + tag = "agents", +)] pub(super) async fn update_identity( State(state): State>, axum::Json(request): axum::Json, @@ -1855,6 +2002,18 @@ pub(super) struct AvatarQuery { } /// Serve the agent's avatar image. +#[utoipa::path( + get, + path = "/agents/avatar", + params( + ("agent_id" = String, Query, description = "Agent ID"), + ), + responses( + (status = 200, description = "Avatar image"), + (status = 404, description = "Avatar or agent not found"), + ), + tag = "agents", +)] pub(super) async fn get_avatar( State(state): State>, Query(query): Query, @@ -1891,6 +2050,21 @@ pub(super) async fn get_avatar( use axum::response::IntoResponse; /// Upload (or replace) the agent's avatar image. +#[utoipa::path( + post, + path = "/agents/avatar", + params( + ("agent_id" = String, Query, description = "Agent ID"), + ), + responses( + (status = 200, body = serde_json::Value, description = "Avatar uploaded successfully"), + (status = 400, description = "Unsupported image type"), + (status = 404, description = "Agent not found"), + (status = 413, description = "Payload too large"), + (status = 500, description = "Internal server error"), + ), + tag = "agents", +)] pub(super) async fn upload_avatar( State(state): State>, Query(query): Query, @@ -1948,6 +2122,18 @@ pub(super) async fn upload_avatar( } /// Delete the agent's avatar image. +#[utoipa::path( + delete, + path = "/agents/avatar", + params( + ("agent_id" = String, Query, description = "Agent ID"), + ), + responses( + (status = 200, body = serde_json::Value), + (status = 404, description = "Agent not found"), + ), + tag = "agents", +)] pub(super) async fn delete_avatar( State(state): State>, Query(query): Query, diff --git a/src/api/bindings.rs b/src/api/bindings.rs index 9b6c5d8de..1b1ed830b 100644 --- a/src/api/bindings.rs +++ b/src/api/bindings.rs @@ -6,7 +6,7 @@ use axum::http::StatusCode; use serde::{Deserialize, Serialize}; use std::sync::Arc; -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct BindingResponse { agent_id: String, channel: String, @@ -20,18 +20,18 @@ pub(super) struct BindingResponse { dm_allowed_users: Vec, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct BindingsListResponse { bindings: Vec, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema, utoipa::IntoParams)] pub(super) struct BindingsQuery { #[serde(default)] agent_id: Option, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct CreateBindingRequest { agent_id: String, channel: String, @@ -56,7 +56,7 @@ pub(super) struct CreateBindingRequest { platform_credentials: Option, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct PlatformCredentials { #[serde(default)] discord_token: Option, @@ -98,7 +98,7 @@ pub(super) struct PlatformCredentials { twitch_refresh_token: Option, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct CreateBindingResponse { success: bool, /// True if platform credentials were added/changed (adapter needs restart). @@ -106,7 +106,7 @@ pub(super) struct CreateBindingResponse { message: String, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct DeleteBindingRequest { agent_id: String, channel: String, @@ -122,13 +122,13 @@ pub(super) struct DeleteBindingRequest { team_id: Option, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct DeleteBindingResponse { success: bool, message: String, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct UpdateBindingRequest { original_agent_id: String, original_channel: String, @@ -163,13 +163,25 @@ pub(super) struct UpdateBindingRequest { dm_allowed_users: Vec, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct UpdateBindingResponse { success: bool, message: String, } /// List all bindings, optionally filtered by agent_id. +#[utoipa::path( + get, + path = "/bindings", + params( + ("agent_id" = Option, Query, description = "Filter by agent ID"), + ), + responses( + (status = 200, body = BindingsListResponse), + (status = 500, description = "Internal server error"), + ), + tag = "bindings", +)] pub(super) async fn list_bindings( State(state): State>, Query(query): Query, @@ -205,6 +217,17 @@ pub(super) async fn list_bindings( } /// Create a new binding (and optionally configure platform credentials). +#[utoipa::path( + post, + path = "/bindings", + request_body = CreateBindingRequest, + responses( + (status = 200, body = CreateBindingResponse), + (status = 400, description = "Invalid request"), + (status = 500, description = "Internal server error"), + ), + tag = "bindings", +)] pub(super) async fn create_binding( State(state): State>, axum::Json(request): axum::Json, @@ -674,6 +697,18 @@ pub(super) async fn create_binding( })) } +/// Update an existing binding. +#[utoipa::path( + put, + path = "/bindings", + request_body = UpdateBindingRequest, + responses( + (status = 200, body = UpdateBindingResponse), + (status = 404, description = "Binding not found or config not found"), + (status = 500, description = "Internal server error"), + ), + tag = "bindings", +)] pub(super) async fn update_binding( State(state): State>, axum::Json(request): axum::Json, @@ -890,6 +925,17 @@ pub(super) async fn update_binding( } /// Delete a binding by matching agent_id + channel + platform-specific identifiers. +#[utoipa::path( + delete, + path = "/bindings", + request_body = DeleteBindingRequest, + responses( + (status = 200, body = DeleteBindingResponse), + (status = 404, description = "Binding not found or config not found"), + (status = 500, description = "Internal server error"), + ), + tag = "bindings", +)] pub(super) async fn delete_binding( State(state): State>, axum::Json(request): axum::Json, diff --git a/src/api/channels.rs b/src/api/channels.rs index cccc7a545..c20ecdd34 100644 --- a/src/api/channels.rs +++ b/src/api/channels.rs @@ -10,7 +10,7 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::sync::Arc; -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct ChannelResponse { agent_id: String, id: String, @@ -21,12 +21,12 @@ pub(super) struct ChannelResponse { created_at: String, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct ChannelsResponse { channels: Vec, } -#[derive(Deserialize, Default)] +#[derive(Deserialize, Default, utoipa::ToSchema, utoipa::IntoParams)] pub(super) struct ListChannelsQuery { #[serde(default)] include_inactive: bool, @@ -57,13 +57,13 @@ fn sort_channels_newest_first(channels: &mut [AgentChannel]) { ); } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct MessagesResponse { items: Vec, has_more: bool, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema, utoipa::IntoParams)] pub(super) struct MessagesQuery { channel_id: String, #[serde(default = "default_message_limit")] @@ -75,20 +75,34 @@ fn default_message_limit() -> i64 { 20 } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct CancelProcessRequest { channel_id: String, process_type: String, process_id: String, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct CancelProcessResponse { success: bool, message: String, } /// List channels across agents, with optional activity and agent filters. +#[utoipa::path( + get, + path = "/channels", + params( + ("include_inactive" = bool, Query, description = "Include inactive channels"), + ("agent_id" = Option, Query, description = "Filter by agent ID"), + ("is_active" = Option, Query, description = "Filter by active state"), + ), + responses( + (status = 200, body = ChannelsResponse), + (status = 500, description = "Internal server error"), + ), + tag = "channels", +)] pub(super) async fn list_channels( State(state): State>, Query(query): Query, @@ -136,6 +150,20 @@ pub(super) async fn list_channels( /// Get the unified timeline for a channel: messages, branch runs, and worker runs /// interleaved chronologically. +#[utoipa::path( + get, + path = "/channels/messages", + params( + ("channel_id" = String, Query, description = "Channel ID"), + ("limit" = i64, Query, description = "Maximum number of messages to return (default: 20, max: 100)"), + ("before" = Option, Query, description = "Pagination cursor for fetching older messages"), + ), + responses( + (status = 200, body = MessagesResponse), + (status = 500, description = "Internal server error"), + ), + tag = "channels", +)] pub(super) async fn channel_messages( State(state): State>, Query(query): Query, @@ -174,6 +202,14 @@ pub(super) async fn channel_messages( } /// Get live status (active workers, branches, completed items) for all channels. +#[utoipa::path( + get, + path = "/channels/status", + responses( + (status = 200, body = serde_json::Value), + ), + tag = "channels", +)] pub(super) async fn channel_status( State(state): State>, ) -> Json> { @@ -193,13 +229,13 @@ pub(super) async fn channel_status( Json(result) } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema, utoipa::IntoParams)] pub(super) struct DeleteChannelQuery { agent_id: String, channel_id: String, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct SetChannelArchiveRequest { agent_id: String, channel_id: String, @@ -207,6 +243,20 @@ pub(super) struct SetChannelArchiveRequest { } /// Delete a channel and its message history. +#[utoipa::path( + delete, + path = "/channels", + params( + ("agent_id" = String, Query, description = "Agent ID that owns the channel"), + ("channel_id" = String, Query, description = "Channel ID to delete"), + ), + responses( + (status = 200, body = serde_json::Value), + (status = 404, description = "Channel or agent not found"), + (status = 500, description = "Internal server error"), + ), + tag = "channels", +)] pub(super) async fn delete_channel( State(state): State>, Query(query): Query, @@ -234,6 +284,17 @@ pub(super) async fn delete_channel( } /// Archive or unarchive a channel without deleting its history. +#[utoipa::path( + post, + path = "/channels/archive", + request_body = SetChannelArchiveRequest, + responses( + (status = 200, body = serde_json::Value), + (status = 404, description = "Channel or agent not found"), + (status = 500, description = "Internal server error"), + ), + tag = "channels", +)] pub(super) async fn set_channel_archive( State(state): State>, Json(request): Json, @@ -274,6 +335,18 @@ fn archive_update_response_payload(archived: bool) -> serde_json::Value { } /// Cancel a running worker or branch via the API. +#[utoipa::path( + post, + path = "/channels/cancel-process", + request_body = CancelProcessRequest, + responses( + (status = 200, body = CancelProcessResponse), + (status = 400, description = "Invalid process type or process ID"), + (status = 404, description = "Process or channel not found"), + (status = 500, description = "Internal server error"), + ), + tag = "channels", +)] pub(super) async fn cancel_process( State(state): State>, Json(request): Json, @@ -379,7 +452,7 @@ pub(super) async fn cancel_process( // ── Prompt Inspect ────────────────────────────────────────────────── -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema, utoipa::IntoParams)] pub(super) struct PromptInspectQuery { channel_id: String, } @@ -388,6 +461,19 @@ pub(super) struct PromptInspectQuery { /// given channel. Returns the rendered system prompt and conversation /// history — useful for debugging prompt construction, coalescing, /// status block content, and context window usage. +#[utoipa::path( + get, + path = "/channels/prompt/inspect", + params( + ("channel_id" = String, Query, description = "Channel ID to inspect"), + ), + responses( + (status = 200, body = serde_json::Value), + (status = 404, description = "Channel not found"), + (status = 500, description = "Internal server error"), + ), + tag = "channels", +)] pub(super) async fn inspect_prompt( State(state): State>, Query(query): Query, @@ -705,13 +791,24 @@ pub(super) async fn inspect_prompt( // ── Prompt Capture Toggle ────────────────────────────────────────── -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct PromptCaptureBody { channel_id: String, enabled: bool, } /// Enable or disable prompt capture for a specific channel. +#[utoipa::path( + post, + path = "/channels/prompt/capture", + request_body = PromptCaptureBody, + responses( + (status = 200, body = serde_json::Value), + (status = 404, description = "Agent or settings not found"), + (status = 500, description = "Internal server error"), + ), + tag = "channels", +)] pub(super) async fn set_prompt_capture( State(state): State>, Json(body): Json, @@ -751,7 +848,7 @@ pub(super) async fn set_prompt_capture( // ── Prompt Snapshot History ──────────────────────────────────────── -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema, utoipa::IntoParams)] pub(super) struct SnapshotListQuery { channel_id: String, #[serde(default = "default_snapshot_limit")] @@ -763,6 +860,20 @@ fn default_snapshot_limit() -> usize { } /// List prompt snapshots for a channel (newest first). +#[utoipa::path( + get, + path = "/channels/prompt/snapshots", + params( + ("channel_id" = String, Query, description = "Channel ID to list snapshots for"), + ("limit" = usize, Query, description = "Maximum number of snapshots to return (default: 50)"), + ), + responses( + (status = 200, body = serde_json::Value), + (status = 404, description = "Snapshot store not found"), + (status = 500, description = "Internal server error"), + ), + tag = "channels", +)] pub(super) async fn list_prompt_snapshots( State(state): State>, Query(query): Query, @@ -782,13 +893,27 @@ pub(super) async fn list_prompt_snapshots( }))) } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema, utoipa::IntoParams)] pub(super) struct SnapshotGetQuery { channel_id: String, timestamp_ms: i64, } /// Retrieve a specific prompt snapshot. +#[utoipa::path( + get, + path = "/channels/prompt/snapshots/get", + params( + ("channel_id" = String, Query, description = "Channel ID the snapshot belongs to"), + ("timestamp_ms" = i64, Query, description = "Snapshot timestamp in milliseconds"), + ), + responses( + (status = 200, body = serde_json::Value), + (status = 404, description = "Snapshot or store not found"), + (status = 500, description = "Internal server error"), + ), + tag = "channels", +)] pub(super) async fn get_prompt_snapshot( State(state): State>, Query(query): Query, diff --git a/src/api/config.rs b/src/api/config.rs index 59a5f4101..080d2c119 100644 --- a/src/api/config.rs +++ b/src/api/config.rs @@ -7,7 +7,7 @@ use axum::http::StatusCode; use serde::{Deserialize, Serialize}; use std::sync::Arc; -#[derive(Serialize, Debug)] +#[derive(Serialize, Debug, utoipa::ToSchema)] pub(super) struct RoutingSection { channel: String, branch: String, @@ -18,7 +18,7 @@ pub(super) struct RoutingSection { rate_limit_cooldown_secs: u64, } -#[derive(Serialize, Debug)] +#[derive(Serialize, Debug, utoipa::ToSchema)] pub(super) struct TuningSection { max_concurrent_branches: usize, max_concurrent_workers: usize, @@ -28,14 +28,14 @@ pub(super) struct TuningSection { history_backfill_count: usize, } -#[derive(Serialize, Debug)] +#[derive(Serialize, Debug, utoipa::ToSchema)] pub(super) struct CompactionSection { background_threshold: f32, aggressive_threshold: f32, emergency_threshold: f32, } -#[derive(Serialize, Debug)] +#[derive(Serialize, Debug, utoipa::ToSchema)] pub(super) struct CortexSection { tick_interval_secs: u64, maintenance_interval_secs: u64, @@ -53,7 +53,7 @@ pub(super) struct CortexSection { maintenance_merge_similarity_threshold: f32, } -#[derive(Serialize, Debug)] +#[derive(Serialize, Debug, utoipa::ToSchema)] pub(super) struct WarmupSection { enabled: bool, eager_embedding_load: bool, @@ -61,7 +61,7 @@ pub(super) struct WarmupSection { startup_delay_secs: u64, } -#[derive(Serialize, Debug)] +#[derive(Serialize, Debug, utoipa::ToSchema)] pub(super) struct CoalesceSection { enabled: bool, debounce_ms: u64, @@ -70,13 +70,13 @@ pub(super) struct CoalesceSection { multi_user_only: bool, } -#[derive(Serialize, Debug)] +#[derive(Serialize, Debug, utoipa::ToSchema)] pub(super) struct MemoryPersistenceSection { enabled: bool, message_interval: usize, } -#[derive(Serialize, Debug)] +#[derive(Serialize, Debug, utoipa::ToSchema)] pub(super) struct BrowserSection { enabled: bool, headless: bool, @@ -85,19 +85,19 @@ pub(super) struct BrowserSection { close_policy: String, } -#[derive(Serialize, Debug)] +#[derive(Serialize, Debug, utoipa::ToSchema)] pub(super) struct ChannelSection { listen_only_mode: bool, } -#[derive(Serialize, Debug)] +#[derive(Serialize, Debug, utoipa::ToSchema)] pub(super) struct SandboxSection { mode: String, writable_paths: Vec, passthrough_env: Vec, } -#[derive(Serialize, Debug)] +#[derive(Serialize, Debug, utoipa::ToSchema)] pub(super) struct ProjectsSection { use_worktrees: bool, worktree_name_template: String, @@ -107,13 +107,13 @@ pub(super) struct ProjectsSection { disk_usage_warning_threshold: u64, } -#[derive(Serialize, Debug)] +#[derive(Serialize, Debug, utoipa::ToSchema)] pub(super) struct DiscordSection { enabled: bool, allow_bot_messages: bool, } -#[derive(Serialize, Debug)] +#[derive(Serialize, Debug, utoipa::ToSchema)] pub(super) struct AgentConfigResponse { routing: RoutingSection, tuning: TuningSection, @@ -129,12 +129,12 @@ pub(super) struct AgentConfigResponse { discord: DiscordSection, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema, utoipa::IntoParams)] pub(super) struct AgentConfigQuery { agent_id: String, } -#[derive(Deserialize, Debug, Default)] +#[derive(Deserialize, Debug, Default, utoipa::ToSchema)] pub(super) struct AgentConfigUpdateRequest { agent_id: String, #[serde(default)] @@ -163,7 +163,7 @@ pub(super) struct AgentConfigUpdateRequest { discord: Option, } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, utoipa::ToSchema)] pub(super) struct RoutingUpdate { channel: Option, branch: Option, @@ -174,7 +174,7 @@ pub(super) struct RoutingUpdate { rate_limit_cooldown_secs: Option, } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, utoipa::ToSchema)] pub(super) struct TuningUpdate { max_concurrent_branches: Option, max_concurrent_workers: Option, @@ -184,14 +184,14 @@ pub(super) struct TuningUpdate { history_backfill_count: Option, } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, utoipa::ToSchema)] pub(super) struct CompactionUpdate { background_threshold: Option, aggressive_threshold: Option, emergency_threshold: Option, } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, utoipa::ToSchema)] pub(super) struct CortexUpdate { tick_interval_secs: Option, maintenance_interval_secs: Option, @@ -209,7 +209,7 @@ pub(super) struct CortexUpdate { maintenance_merge_similarity_threshold: Option, } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, utoipa::ToSchema)] pub(super) struct WarmupUpdate { enabled: Option, eager_embedding_load: Option, @@ -217,7 +217,7 @@ pub(super) struct WarmupUpdate { startup_delay_secs: Option, } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, utoipa::ToSchema)] pub(super) struct CoalesceUpdate { enabled: Option, debounce_ms: Option, @@ -226,13 +226,13 @@ pub(super) struct CoalesceUpdate { multi_user_only: Option, } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, utoipa::ToSchema)] pub(super) struct MemoryPersistenceUpdate { enabled: Option, message_interval: Option, } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, utoipa::ToSchema)] pub(super) struct BrowserUpdate { enabled: Option, headless: Option, @@ -241,19 +241,19 @@ pub(super) struct BrowserUpdate { close_policy: Option, } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, utoipa::ToSchema)] pub(super) struct ChannelUpdate { listen_only_mode: Option, } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, utoipa::ToSchema)] pub(super) struct SandboxUpdate { mode: Option, writable_paths: Option>, passthrough_env: Option>, } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, utoipa::ToSchema)] pub(super) struct ProjectsUpdate { use_worktrees: Option, worktree_name_template: Option, @@ -263,13 +263,23 @@ pub(super) struct ProjectsUpdate { disk_usage_warning_threshold: Option, } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, utoipa::ToSchema)] pub(super) struct DiscordUpdate { allow_bot_messages: Option, } /// Get the resolved configuration for an agent. /// Reads live values from the agent's RuntimeConfig (hot-reloaded via ArcSwap). +#[utoipa::path( + get, + path = "/agents/config", + params(AgentConfigQuery), + responses( + (status = 200, body = AgentConfigResponse), + (status = 404, description = "Agent not found"), + ), + tag = "config", +)] pub(super) async fn get_agent_config( State(state): State>, Query(query): Query, @@ -399,6 +409,17 @@ pub(super) async fn get_agent_config( /// Update agent configuration by editing config.toml with toml_edit. /// This preserves formatting and comments while writing the new values. +#[utoipa::path( + put, + path = "/agents/config", + request_body = AgentConfigUpdateRequest, + responses( + (status = 200, body = AgentConfigResponse), + (status = 400, description = "Invalid request"), + (status = 500, description = "Internal server error"), + ), + tag = "config", +)] pub(super) async fn update_agent_config( State(state): State>, axum::Json(request): axum::Json, diff --git a/src/api/cortex.rs b/src/api/cortex.rs index b103c4d35..a670cf941 100644 --- a/src/api/cortex.rs +++ b/src/api/cortex.rs @@ -14,19 +14,19 @@ use serde::{Deserialize, Serialize}; use std::convert::Infallible; use std::sync::Arc; -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct CortexEventsResponse { events: Vec, total: i64, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct CortexChatMessagesResponse { messages: Vec, thread_id: String, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct CortexChatMessagesQuery { agent_id: String, /// If omitted, loads the latest thread. @@ -39,7 +39,7 @@ fn default_cortex_chat_limit() -> i64 { 50 } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct CortexChatSendRequest { agent_id: String, thread_id: String, @@ -47,7 +47,7 @@ pub(super) struct CortexChatSendRequest { channel_id: Option, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct CortexEventsQuery { agent_id: String, #[serde(default = "default_cortex_events_limit")] @@ -72,6 +72,21 @@ fn map_cortex_chat_send_error(error: &CortexChatSendError) -> StatusCode { /// Load persisted cortex chat history for a thread. /// If no thread_id is provided, loads the latest thread. /// If no threads exist, returns an empty list with a fresh thread_id. +#[utoipa::path( + get, + path = "/cortex-chat/messages", + params( + ("agent_id" = String, Query, description = "Agent ID"), + ("thread_id" = Option, Query, description = "Thread ID (omit for latest)"), + ("limit" = i64, Query, description = "Maximum messages to return (default: 50, max: 200)"), + ), + responses( + (status = 200, body = CortexChatMessagesResponse), + (status = 404, description = "Agent not found"), + (status = 500, description = "Internal server error"), + ), + tag = "cortex", +)] pub(super) async fn cortex_chat_messages( State(state): State>, Query(query): Query, @@ -112,6 +127,18 @@ pub(super) async fn cortex_chat_messages( /// - `tool_completed` — a tool call finished (with result preview) /// - `done` — full response text /// - `error` — if something went wrong +#[utoipa::path( + post, + path = "/cortex-chat/send", + request_body = CortexChatSendRequest, + responses( + (status = 200, description = "SSE stream of chat events"), + (status = 404, description = "Agent not found"), + (status = 409, description = "Cortex chat session busy"), + (status = 500, description = "Internal server error"), + ), + tag = "cortex", +)] pub(super) async fn cortex_chat_send( State(state): State>, axum::Json(request): axum::Json, @@ -164,23 +191,36 @@ pub(super) async fn cortex_chat_send( // -- Thread management -- -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct CortexChatThreadsResponse { threads: Vec, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct CortexChatThreadsQuery { agent_id: String, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct CortexChatDeleteThreadRequest { agent_id: String, thread_id: String, } /// List all cortex chat threads for an agent, newest first. +#[utoipa::path( + get, + path = "/cortex-chat/threads", + params( + ("agent_id" = String, Query, description = "Agent ID"), + ), + responses( + (status = 200, body = CortexChatThreadsResponse), + (status = 404, description = "Agent not found"), + (status = 500, description = "Internal server error"), + ), + tag = "cortex", +)] pub(super) async fn cortex_chat_threads( State(state): State>, Query(query): Query, @@ -198,6 +238,17 @@ pub(super) async fn cortex_chat_threads( } /// Delete a cortex chat thread and all its messages. +#[utoipa::path( + delete, + path = "/cortex-chat/thread", + request_body = CortexChatDeleteThreadRequest, + responses( + (status = 204, description = "Thread deleted successfully"), + (status = 404, description = "Agent or thread not found"), + (status = 500, description = "Internal server error"), + ), + tag = "cortex", +)] pub(super) async fn cortex_chat_delete_thread( State(state): State>, axum::Json(request): axum::Json, @@ -219,6 +270,22 @@ pub(super) async fn cortex_chat_delete_thread( } /// List cortex events for an agent with optional type filter, newest first. +#[utoipa::path( + get, + path = "/cortex/events", + params( + ("agent_id" = String, Query, description = "Agent ID"), + ("limit" = i64, Query, description = "Maximum events to return (default: 50, max: 200)"), + ("offset" = i64, Query, description = "Offset for pagination (default: 0)"), + ("event_type" = Option, Query, description = "Filter by event type"), + ), + responses( + (status = 200, body = CortexEventsResponse), + (status = 404, description = "Agent not found"), + (status = 500, description = "Internal server error"), + ), + tag = "cortex", +)] pub(super) async fn cortex_events( State(state): State>, Query(query): Query, diff --git a/src/api/cron.rs b/src/api/cron.rs index f41e42cfc..204a66f61 100644 --- a/src/api/cron.rs +++ b/src/api/cron.rs @@ -7,12 +7,12 @@ use serde::{Deserialize, Serialize}; use std::str::FromStr; use std::sync::Arc; -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema, utoipa::IntoParams)] pub(super) struct CronQuery { agent_id: String, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema, utoipa::IntoParams)] pub(super) struct CronExecutionsQuery { agent_id: String, #[serde(default)] @@ -25,7 +25,7 @@ fn default_cron_executions_limit() -> i64 { 50 } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, utoipa::ToSchema)] pub(super) struct CreateCronRequest { agent_id: String, id: String, @@ -55,26 +55,26 @@ fn default_enabled() -> bool { true } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct DeleteCronRequest { agent_id: String, cron_id: String, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct TriggerCronRequest { agent_id: String, cron_id: String, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct ToggleCronRequest { agent_id: String, cron_id: String, enabled: bool, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] struct CronJobWithStats { id: String, prompt: String, @@ -90,24 +90,37 @@ struct CronJobWithStats { last_executed_at: Option, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct CronListResponse { jobs: Vec, timezone: String, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct CronExecutionsResponse { executions: Vec, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct CronActionResponse { success: bool, message: String, } /// List all cron jobs for an agent with execution statistics. +#[utoipa::path( + get, + path = "/agents/cron", + params( + ("agent_id" = String, Query, description = "Agent ID"), + ), + responses( + (status = 200, body = CronListResponse), + (status = 404, description = "Agent not found"), + (status = 500, description = "Internal server error"), + ), + tag = "cron", +)] pub(super) async fn list_cron_jobs( State(state): State>, Query(query): Query, @@ -154,6 +167,21 @@ pub(super) async fn list_cron_jobs( } /// Get execution history for cron jobs. +#[utoipa::path( + get, + path = "/agents/cron/executions", + params( + ("agent_id" = String, Query, description = "Agent ID"), + ("cron_id" = Option, Query, description = "Cron job ID (optional)"), + ("limit" = i64, Query, description = "Maximum number of executions to return (default 50)"), + ), + responses( + (status = 200, body = CronExecutionsResponse), + (status = 404, description = "Agent not found"), + (status = 500, description = "Internal server error"), + ), + tag = "cron", +)] pub(super) async fn cron_executions( State(state): State>, Query(query): Query, @@ -272,6 +300,18 @@ fn validate_cron_request(request: &CreateCronRequest) -> Result<(), (StatusCode, } /// Create or update a cron job. +#[utoipa::path( + post, + path = "/agents/cron", + request_body = CreateCronRequest, + responses( + (status = 200, body = CronActionResponse), + (status = 400, description = "Invalid request"), + (status = 404, description = "Agent not found"), + (status = 500, description = "Internal server error"), + ), + tag = "cron", +)] pub(super) async fn create_or_update_cron( State(state): State>, Json(request): Json, @@ -352,6 +392,20 @@ pub(super) async fn create_or_update_cron( } /// Delete a cron job. +#[utoipa::path( + delete, + path = "/agents/cron", + params( + ("agent_id" = String, Query, description = "Agent ID"), + ("cron_id" = String, Query, description = "Cron job ID to delete"), + ), + responses( + (status = 200, body = CronActionResponse), + (status = 404, description = "Agent not found"), + (status = 500, description = "Internal server error"), + ), + tag = "cron", +)] pub(super) async fn delete_cron( State(state): State>, Query(query): Query, @@ -378,6 +432,17 @@ pub(super) async fn delete_cron( } /// Trigger a cron job immediately. +#[utoipa::path( + post, + path = "/agents/cron/trigger", + request_body = TriggerCronRequest, + responses( + (status = 200, body = CronActionResponse), + (status = 404, description = "Agent not found"), + (status = 500, description = "Internal server error"), + ), + tag = "cron", +)] pub(super) async fn trigger_cron( State(state): State>, Json(request): Json, @@ -399,6 +464,17 @@ pub(super) async fn trigger_cron( } /// Enable or disable a cron job. +#[utoipa::path( + put, + path = "/agents/cron/toggle", + request_body = ToggleCronRequest, + responses( + (status = 200, body = CronActionResponse), + (status = 404, description = "Agent not found"), + (status = 500, description = "Internal server error"), + ), + tag = "cron", +)] pub(super) async fn toggle_cron( State(state): State>, Json(request): Json, diff --git a/src/api/factory.rs b/src/api/factory.rs index bebc94aef..906c684e8 100644 --- a/src/api/factory.rs +++ b/src/api/factory.rs @@ -8,16 +8,32 @@ use axum::response::IntoResponse; use crate::factory::presets::PresetRegistry; /// List all available preset archetypes (metadata only). -/// -/// `GET /api/factory/presets` +#[utoipa::path( + get, + path = "/factory/presets", + responses( + (status = 200, body = Vec), + ), + tag = "factory", +)] pub async fn list_presets() -> impl IntoResponse { let presets = PresetRegistry::list(); Json(serde_json::json!({ "presets": presets })) } /// Load a full preset by ID, including soul, identity, and role content. -/// -/// `GET /api/factory/presets/:id` +#[utoipa::path( + get, + path = "/factory/presets/{id}", + params( + ("id" = String, Path, description = "Preset ID"), + ), + responses( + (status = 200, body = crate::factory::presets::Preset), + (status = 404, description = "Preset not found"), + ), + tag = "factory", +)] pub async fn get_preset(Path(preset_id): Path) -> Result { let preset = PresetRegistry::load(&preset_id).ok_or(StatusCode::NOT_FOUND)?; Ok(Json(serde_json::json!({ "preset": preset }))) diff --git a/src/api/ingest.rs b/src/api/ingest.rs index 29a4cecdd..da5ef1a17 100644 --- a/src/api/ingest.rs +++ b/src/api/ingest.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; use std::path::Path; use std::sync::Arc; -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct IngestFileInfo { content_hash: String, filename: String, @@ -19,33 +19,46 @@ pub(super) struct IngestFileInfo { completed_at: Option, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct IngestFilesResponse { files: Vec, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct IngestUploadResponse { uploaded: Vec, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct IngestDeleteResponse { success: bool, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema, utoipa::IntoParams)] pub(super) struct IngestQuery { agent_id: String, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema, utoipa::IntoParams)] pub(super) struct IngestDeleteQuery { agent_id: String, content_hash: String, } /// List ingested files with progress info for in-progress ones. +#[utoipa::path( + get, + path = "/agents/ingest/files", + params( + ("agent_id" = String, Query, description = "Agent ID"), + ), + responses( + (status = 200, body = IngestFilesResponse), + (status = 404, description = "Agent not found"), + (status = 500, description = "Internal server error"), + ), + tag = "ingest", +)] pub(super) async fn list_ingest_files( State(state): State>, Query(query): Query, @@ -95,6 +108,20 @@ pub(super) async fn list_ingest_files( } /// Upload one or more files to the agent's ingest directory. +#[utoipa::path( + post, + path = "/agents/ingest/files", + params( + ("agent_id" = String, Query, description = "Agent ID"), + ), + responses( + (status = 200, body = IngestUploadResponse), + (status = 400, description = "Invalid request"), + (status = 404, description = "Agent not found"), + (status = 500, description = "Internal server error"), + ), + tag = "ingest", +)] pub(super) async fn upload_ingest_file( State(state): State>, Query(query): Query, @@ -195,6 +222,20 @@ pub(super) async fn upload_ingest_file( } /// Delete a completed ingestion file record from history. +#[utoipa::path( + delete, + path = "/agents/ingest/files", + params( + ("agent_id" = String, Query, description = "Agent ID"), + ("content_hash" = String, Query, description = "Content hash of the file to delete"), + ), + responses( + (status = 200, body = IngestDeleteResponse), + (status = 404, description = "Agent not found"), + (status = 500, description = "Internal server error"), + ), + tag = "ingest", +)] pub(super) async fn delete_ingest_file( State(state): State>, Query(query): Query, diff --git a/src/api/links.rs b/src/api/links.rs index ebac742cf..c71b77344 100644 --- a/src/api/links.rs +++ b/src/api/links.rs @@ -11,12 +11,31 @@ use serde::{Deserialize, Serialize}; use std::sync::Arc; /// List all links in the instance. +#[utoipa::path( + get, + path = "/links", + responses( + (status = 200, body = serde_json::Value), + ), + tag = "links", +)] pub async fn list_links(State(state): State>) -> impl IntoResponse { let links = state.agent_links.load(); Json(serde_json::json!({ "links": &**links })) } /// Get links for a specific agent. +#[utoipa::path( + get, + path = "/links/agent/{agent_id}", + params( + ("agent_id" = String, Path, description = "Agent ID"), + ), + responses( + (status = 200, body = serde_json::Value), + ), + tag = "links", +)] pub async fn agent_links( State(state): State>, Path(agent_id): Path, @@ -27,7 +46,7 @@ pub async fn agent_links( } /// Topology response for graph rendering. -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, utoipa::ToSchema)] struct TopologyResponse { agents: Vec, humans: Vec, @@ -35,7 +54,7 @@ struct TopologyResponse { groups: Vec, } -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, utoipa::ToSchema)] struct TopologyHuman { id: String, #[serde(skip_serializing_if = "Option::is_none")] @@ -46,9 +65,17 @@ struct TopologyHuman { bio: Option, #[serde(skip_serializing_if = "Option::is_none")] description: Option, + #[serde(skip_serializing_if = "Option::is_none")] + discord_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + telegram_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + slack_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + email: Option, } -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, utoipa::ToSchema)] struct TopologyAgent { id: String, name: String, @@ -58,7 +85,7 @@ struct TopologyAgent { role: Option, } -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, utoipa::ToSchema)] struct TopologyLink { from: String, to: String, @@ -66,7 +93,7 @@ struct TopologyLink { kind: String, } -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, utoipa::ToSchema)] struct TopologyGroup { name: String, agent_ids: Vec, @@ -75,6 +102,14 @@ struct TopologyGroup { } /// Get the full agent topology for graph rendering. +#[utoipa::path( + get, + path = "/topology", + responses( + (status = 200, body = TopologyResponse), + ), + tag = "links", +)] pub async fn topology(State(state): State>) -> impl IntoResponse { let agent_configs = state.agent_configs.load(); let agents: Vec = agent_configs @@ -120,6 +155,10 @@ pub async fn topology(State(state): State>) -> impl IntoResponse { role: h.role.clone(), bio: h.bio.clone(), description: h.description.clone(), + discord_id: h.discord_id.clone(), + telegram_id: h.telegram_id.clone(), + slack_id: h.slack_id.clone(), + email: h.email.clone(), }) .collect(); @@ -133,7 +172,7 @@ pub async fn topology(State(state): State>) -> impl IntoResponse { // -- Write endpoints -- -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, utoipa::ToSchema)] pub struct CreateLinkRequest { pub from: String, pub to: String, @@ -151,13 +190,25 @@ fn default_kind() -> String { "peer".into() } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, utoipa::ToSchema)] pub struct UpdateLinkRequest { pub direction: Option, pub kind: Option, } /// Create a new link between two agents. Persists to config.toml and updates in-memory state. +#[utoipa::path( + post, + path = "/links", + request_body = CreateLinkRequest, + responses( + (status = 201, body = serde_json::Value), + (status = 400, description = "Invalid request"), + (status = 404, description = "Agent or human not found"), + (status = 409, description = "Link already exists"), + ), + tag = "links", +)] pub async fn create_link( State(state): State>, Json(request): Json, @@ -263,6 +314,21 @@ pub async fn create_link( } /// Update a link's properties. Identifies the link by from/to agent IDs in the path. +#[utoipa::path( + put, + path = "/links/{from}/{to}", + params( + ("from" = String, Path, description = "Source agent"), + ("to" = String, Path, description = "Target agent"), + ), + request_body = UpdateLinkRequest, + responses( + (status = 200, body = serde_json::Value), + (status = 400, description = "Invalid request"), + (status = 404, description = "Link not found"), + ), + tag = "links", +)] pub async fn update_link( State(state): State>, Path((from, to)): Path<(String, String)>, @@ -334,6 +400,19 @@ pub async fn update_link( } /// Delete a link between two agents. +#[utoipa::path( + delete, + path = "/links/{from}/{to}", + params( + ("from" = String, Path, description = "Source agent"), + ("to" = String, Path, description = "Target agent"), + ), + responses( + (status = 204), + (status = 404, description = "Link not found"), + ), + tag = "links", +)] pub async fn delete_link( State(state): State>, Path((from, to)): Path<(String, String)>, @@ -400,7 +479,7 @@ pub async fn delete_link( // -- Group CRUD -- -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, utoipa::ToSchema)] pub struct CreateGroupRequest { pub name: String, #[serde(default)] @@ -408,7 +487,7 @@ pub struct CreateGroupRequest { pub color: Option, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, utoipa::ToSchema)] pub struct UpdateGroupRequest { pub name: Option, pub agent_ids: Option>, @@ -416,12 +495,31 @@ pub struct UpdateGroupRequest { } /// List all groups. +#[utoipa::path( + get, + path = "/links/groups", + responses( + (status = 200, body = serde_json::Value), + ), + tag = "links", +)] pub async fn list_groups(State(state): State>) -> impl IntoResponse { let groups = state.agent_groups.load(); Json(serde_json::json!({ "groups": &**groups })) } /// Create a visual agent group. +#[utoipa::path( + post, + path = "/links/groups", + request_body = CreateGroupRequest, + responses( + (status = 201, body = serde_json::Value), + (status = 400, description = "Invalid request"), + (status = 409, description = "Group already exists"), + ), + tag = "links", +)] pub async fn create_group( State(state): State>, Json(request): Json, @@ -492,6 +590,21 @@ pub async fn create_group( } /// Update a group by name. +#[utoipa::path( + put, + path = "/links/groups/{group_name}", + params( + ("group_name" = String, Path, description = "Group name"), + ), + request_body = UpdateGroupRequest, + responses( + (status = 200, body = serde_json::Value), + (status = 400, description = "Invalid request"), + (status = 404, description = "Group not found"), + (status = 409, description = "Group name conflict"), + ), + tag = "links", +)] pub async fn update_group( State(state): State>, Path(group_name): Path, @@ -580,6 +693,18 @@ pub async fn update_group( } /// Delete a group by name. +#[utoipa::path( + delete, + path = "/links/groups/{group_name}", + params( + ("group_name" = String, Path, description = "Group name"), + ), + responses( + (status = 204), + (status = 404, description = "Group not found"), + ), + tag = "links", +)] pub async fn delete_group( State(state): State>, Path(group_name): Path, @@ -639,7 +764,7 @@ pub async fn delete_group( // -- Human CRUD -- -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, utoipa::ToSchema)] pub struct CreateHumanRequest { pub id: String, pub display_name: Option, @@ -652,7 +777,7 @@ pub struct CreateHumanRequest { pub email: Option, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, utoipa::ToSchema)] pub struct UpdateHumanRequest { pub display_name: Option, pub role: Option, @@ -665,6 +790,14 @@ pub struct UpdateHumanRequest { } /// List all humans. +#[utoipa::path( + get, + path = "/links/humans", + responses( + (status = 200, body = serde_json::Value), + ), + tag = "links", +)] pub async fn list_humans(State(state): State>) -> impl IntoResponse { let humans = state.agent_humans.load(); Json(serde_json::json!({ "humans": &**humans })) @@ -717,6 +850,17 @@ fn ensure_humans_in_doc(doc: &mut toml_edit::DocumentMut, humans: &[crate::confi } /// Create an org-level human. +#[utoipa::path( + post, + path = "/links/humans", + request_body = CreateHumanRequest, + responses( + (status = 201, body = serde_json::Value), + (status = 400, description = "Invalid request"), + (status = 409, description = "Human ID already exists"), + ), + tag = "links", +)] pub async fn create_human( State(state): State>, Json(request): Json, @@ -848,6 +992,19 @@ pub async fn create_human( } /// Update a human by ID. +#[utoipa::path( + put, + path = "/links/humans/{human_id}", + params( + ("human_id" = String, Path, description = "Human ID"), + ), + request_body = UpdateHumanRequest, + responses( + (status = 200, body = serde_json::Value), + (status = 404, description = "Human not found"), + ), + tag = "links", +)] pub async fn update_human( State(state): State>, Path(human_id): Path, @@ -1019,6 +1176,18 @@ pub async fn update_human( } /// Delete a human by ID. Also removes any links involving this human. +#[utoipa::path( + delete, + path = "/links/humans/{human_id}", + params( + ("human_id" = String, Path, description = "Human ID"), + ), + responses( + (status = 204), + (status = 404, description = "Human not found"), + ), + tag = "links", +)] pub async fn delete_human( State(state): State>, Path(human_id): Path, diff --git a/src/api/mcp.rs b/src/api/mcp.rs index f3291b0b4..8e7152db5 100644 --- a/src/api/mcp.rs +++ b/src/api/mcp.rs @@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::sync::Arc; -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct CreateMcpServerRequest { pub name: String, pub transport: String, @@ -32,7 +32,7 @@ fn default_enabled() -> bool { true } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct McpServerInfo { pub name: String, pub transport: String, @@ -40,19 +40,28 @@ pub(super) struct McpServerInfo { pub state: String, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct McpAgentStatus { pub agent_id: String, pub servers: Vec, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct MutationResponse { pub success: bool, pub message: String, } /// GET /api/mcp/servers — list all configured MCP servers from config.toml. +#[utoipa::path( + get, + path = "/mcp/servers", + responses( + (status = 200, body = Vec), + (status = 500, description = "Internal server error"), + ), + tag = "mcp", +)] pub(super) async fn list_mcp_servers( State(state): State>, ) -> Result>, StatusCode> { @@ -108,6 +117,16 @@ pub(super) async fn list_mcp_servers( } /// POST /api/mcp/servers — add a new MCP server definition to config.toml. +#[utoipa::path( + post, + path = "/mcp/servers", + request_body = CreateMcpServerRequest, + responses( + (status = 200, body = MutationResponse), + (status = 500, description = "Internal server error"), + ), + tag = "mcp", +)] pub(super) async fn create_mcp_server( State(state): State>, Json(request): Json, @@ -206,6 +225,16 @@ pub(super) async fn create_mcp_server( } /// PUT /api/mcp/servers — update an existing MCP server definition. +#[utoipa::path( + put, + path = "/mcp/servers", + request_body = CreateMcpServerRequest, + responses( + (status = 200, body = MutationResponse), + (status = 500, description = "Internal server error"), + ), + tag = "mcp", +)] pub(super) async fn update_mcp_server( State(state): State>, Json(request): Json, @@ -286,6 +315,18 @@ pub(super) async fn update_mcp_server( } /// DELETE /api/mcp/servers/{name} — remove a server definition from config.toml. +#[utoipa::path( + delete, + path = "/mcp/servers/{name}", + params( + ("name" = String, Path, description = "Server name"), + ), + responses( + (status = 200, body = MutationResponse), + (status = 500, description = "Internal server error"), + ), + tag = "mcp", +)] pub(super) async fn delete_mcp_server( State(state): State>, Path(server_name): Path, @@ -348,6 +389,14 @@ pub(super) async fn delete_mcp_server( } /// GET /api/mcp/status — per-agent MCP connection status. +#[utoipa::path( + get, + path = "/mcp/status", + responses( + (status = 200, body = Vec), + ), + tag = "mcp", +)] pub(super) async fn mcp_status(State(state): State>) -> Json> { let managers = state.mcp_managers.load(); let mut result = Vec::with_capacity(managers.len()); @@ -379,6 +428,18 @@ pub(super) async fn mcp_status(State(state): State>) -> Json>, Path(server_name): Path, diff --git a/src/api/memories.rs b/src/api/memories.rs index e3e50e286..e8441b055 100644 --- a/src/api/memories.rs +++ b/src/api/memories.rs @@ -9,31 +9,31 @@ use axum::http::StatusCode; use serde::{Deserialize, Serialize}; use std::sync::Arc; -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct MemoriesListResponse { memories: Vec, total: usize, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct MemoriesSearchResponse { results: Vec, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct MemoryGraphResponse { nodes: Vec, edges: Vec, total: usize, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct MemoryGraphNeighborsResponse { nodes: Vec, edges: Vec, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema, utoipa::IntoParams)] pub(super) struct MemoriesListQuery { agent_id: String, #[serde(default = "default_memories_limit")] @@ -76,7 +76,7 @@ pub(super) fn parse_memory_type(type_str: &str) -> Option { } } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema, utoipa::IntoParams)] pub(super) struct MemoriesSearchQuery { agent_id: String, q: String, @@ -90,7 +90,7 @@ fn default_search_limit() -> usize { 20 } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema, utoipa::IntoParams)] pub(super) struct MemoryGraphQuery { agent_id: String, #[serde(default = "default_graph_limit")] @@ -107,7 +107,7 @@ fn default_graph_limit() -> i64 { 200 } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema, utoipa::IntoParams)] pub(super) struct MemoryGraphNeighborsQuery { agent_id: String, memory_id: String, @@ -123,6 +123,23 @@ fn default_neighbor_depth() -> u32 { } /// List memories for an agent with sorting, filtering, and pagination. +#[utoipa::path( + get, + path = "/agents/memories", + params( + ("agent_id" = String, Query, description = "Agent ID"), + ("limit" = i64, Query, description = "Maximum number of results to return (default 50, max 200)"), + ("offset" = usize, Query, description = "Number of results to skip for pagination"), + ("memory_type" = Option, Query, description = "Filter by memory type (fact, preference, decision, identity, event, observation, goal, todo)"), + ("sort" = String, Query, description = "Sort order: recent, importance, most_accessed (default: recent)"), + ), + responses( + (status = 200, body = MemoriesListResponse), + (status = 404, description = "Agent not found"), + (status = 500, description = "Internal server error"), + ), + tag = "memories", +)] pub(super) async fn list_memories( State(state): State>, Query(query): Query, @@ -151,6 +168,22 @@ pub(super) async fn list_memories( } /// Search memories using hybrid search (vector + FTS + graph). +#[utoipa::path( + get, + path = "/agents/memories/search", + params( + ("agent_id" = String, Query, description = "Agent ID"), + ("q" = String, Query, description = "Search query string"), + ("limit" = usize, Query, description = "Maximum number of results to return (default 20, max 100)"), + ("memory_type" = Option, Query, description = "Filter by memory type"), + ), + responses( + (status = 200, body = MemoriesSearchResponse), + (status = 404, description = "Agent not found"), + (status = 500, description = "Internal server error"), + ), + tag = "memories", +)] pub(super) async fn search_memories( State(state): State>, Query(query): Query, @@ -176,6 +209,23 @@ pub(super) async fn search_memories( } /// Get a subgraph of memories: nodes + all edges between them. +#[utoipa::path( + get, + path = "/agents/memories/graph", + params( + ("agent_id" = String, Query, description = "Agent ID"), + ("limit" = i64, Query, description = "Maximum number of nodes to return (default 200, max 500)"), + ("offset" = usize, Query, description = "Number of nodes to skip for pagination"), + ("memory_type" = Option, Query, description = "Filter by memory type"), + ("sort" = String, Query, description = "Sort order: recent, importance, most_accessed (default: recent)"), + ), + responses( + (status = 200, body = MemoryGraphResponse), + (status = 404, description = "Agent not found"), + (status = 500, description = "Internal server error"), + ), + tag = "memories", +)] pub(super) async fn memory_graph( State(state): State>, Query(query): Query, @@ -218,6 +268,22 @@ pub(super) async fn memory_graph( /// Get the neighbors of a specific memory node. Returns new nodes /// and edges not already present in the client's graph. +#[utoipa::path( + get, + path = "/agents/memories/graph/neighbors", + params( + ("agent_id" = String, Query, description = "Agent ID"), + ("memory_id" = String, Query, description = "Memory ID to get neighbors for"), + ("depth" = u32, Query, description = "Neighbor traversal depth (default 1, max 3)"), + ("exclude" = Option, Query, description = "Comma-separated list of memory IDs to exclude from results"), + ), + responses( + (status = 200, body = MemoryGraphNeighborsResponse), + (status = 404, description = "Agent not found"), + (status = 500, description = "Internal server error"), + ), + tag = "memories", +)] pub(super) async fn memory_graph_neighbors( State(state): State>, Query(query): Query, diff --git a/src/api/messaging.rs b/src/api/messaging.rs index e0e5b26f7..6aad16fe3 100644 --- a/src/api/messaging.rs +++ b/src/api/messaging.rs @@ -9,13 +9,13 @@ use std::sync::Arc; // Re-export E.164 validation from messaging target module pub use crate::messaging::target::is_valid_e164; -#[derive(Serialize, Clone)] +#[derive(Serialize, Clone, utoipa::ToSchema)] pub(super) struct PlatformStatus { configured: bool, enabled: bool, } -#[derive(Serialize, Clone)] +#[derive(Serialize, Clone, utoipa::ToSchema)] pub(super) struct AdapterInstanceStatus { platform: String, /// `None` means the default instance for the platform. @@ -26,7 +26,7 @@ pub(super) struct AdapterInstanceStatus { binding_count: usize, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct MessagingStatusResponse { discord: PlatformStatus, slack: PlatformStatus, @@ -39,14 +39,14 @@ pub(super) struct MessagingStatusResponse { instances: Vec, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct DisconnectPlatformRequest { platform: String, #[serde(default)] adapter: Option, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct TogglePlatformRequest { platform: String, #[serde(default)] @@ -54,7 +54,7 @@ pub(super) struct TogglePlatformRequest { enabled: bool, } -#[derive(Deserialize, Default)] +#[derive(Deserialize, Default, utoipa::ToSchema)] pub(super) struct InstanceCredentials { #[serde(default)] discord_token: Option, @@ -114,7 +114,7 @@ pub(super) struct InstanceCredentials { signal_dm_allowed_users: Option, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct CreateMessagingInstanceRequest { platform: String, #[serde(default)] @@ -125,14 +125,14 @@ pub(super) struct CreateMessagingInstanceRequest { credentials: InstanceCredentials, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct DeleteMessagingInstanceRequest { platform: String, #[serde(default)] name: Option, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct MessagingInstanceActionResponse { success: bool, message: String, @@ -349,6 +349,15 @@ fn parse_signal_credentials( } /// Get which messaging platforms are configured and enabled. +#[utoipa::path( + get, + path = "/messaging/status", + responses( + (status = 200, body = MessagingStatusResponse), + (status = 500, description = "Internal server error"), + ), + tag = "messaging", +)] pub(super) async fn messaging_status( State(state): State>, ) -> Result, StatusCode> { @@ -909,6 +918,16 @@ pub(super) async fn messaging_status( /// When `adapter` is set, only the specified named instance is disconnected. /// When `adapter` is absent, the entire platform section (default + all named /// instances) is removed. +#[utoipa::path( + post, + path = "/messaging/disconnect", + request_body = DisconnectPlatformRequest, + responses( + (status = 200, body = serde_json::Value), + (status = 500, description = "Internal server error"), + ), + tag = "messaging", +)] pub(super) async fn disconnect_platform( State(state): State>, Json(request): Json, @@ -1091,6 +1110,16 @@ pub(super) async fn disconnect_platform( /// Toggle a messaging platform's enabled state. When disabling, shuts down the /// adapter. When enabling, reads credentials from config and hot-starts it. +#[utoipa::path( + post, + path = "/messaging/toggle", + request_body = TogglePlatformRequest, + responses( + (status = 200, body = serde_json::Value), + (status = 500, description = "Internal server error"), + ), + tag = "messaging", +)] pub(super) async fn toggle_platform( State(state): State>, Json(request): Json, @@ -1580,6 +1609,16 @@ pub(super) async fn toggle_platform( /// If `name` is `None`, creates/updates the default instance (root-level credentials). /// If `name` is `Some`, adds a `[[messaging..instances]]` entry. /// Starts the adapter at runtime and reloads bindings. +#[utoipa::path( + post, + path = "/messaging/instances", + request_body = CreateMessagingInstanceRequest, + responses( + (status = 200, body = MessagingInstanceActionResponse), + (status = 500, description = "Internal server error"), + ), + tag = "messaging", +)] pub(super) async fn create_messaging_instance( State(state): State>, Json(request): Json, @@ -2022,6 +2061,16 @@ pub(super) async fn create_messaging_instance( /// /// Removes the instance entry from config.toml, removes bindings targeting /// that adapter, and shuts down the runtime adapter. +#[utoipa::path( + delete, + path = "/messaging/instances", + request_body = DeleteMessagingInstanceRequest, + responses( + (status = 200, body = MessagingInstanceActionResponse), + (status = 500, description = "Internal server error"), + ), + tag = "messaging", +)] pub(super) async fn delete_messaging_instance( State(state): State>, Json(request): Json, diff --git a/src/api/models.rs b/src/api/models.rs index f988bf6b6..ffe7757bb 100644 --- a/src/api/models.rs +++ b/src/api/models.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::sync::Arc; -#[derive(Serialize, Clone)] +#[derive(Serialize, Clone, utoipa::ToSchema)] pub(super) struct ModelInfo { /// Full routing string (e.g. "openrouter/anthropic/claude-sonnet-4") id: String, @@ -25,18 +25,18 @@ pub(super) struct ModelInfo { input_audio: bool, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct ModelsResponse { models: Vec, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema, utoipa::IntoParams)] pub(super) struct ModelsQuery { provider: Option, capability: Option, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] struct ModelsDevProvider { #[allow(dead_code)] id: Option, @@ -46,7 +46,7 @@ struct ModelsDevProvider { models: HashMap, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] struct ModelsDevModel { #[allow(dead_code)] id: Option, @@ -60,12 +60,12 @@ struct ModelsDevModel { status: Option, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] struct ModelsDevLimit { context: u64, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] struct ModelsDevModalities { input: Option>, output: Option>, @@ -351,6 +351,19 @@ pub(super) async fn configured_providers(config_path: &std::path::Path) -> Vec<& providers } +#[utoipa::path( + get, + path = "/models", + params( + ("provider" = Option, Query, description = "Filter by provider ID"), + ("capability" = Option, Query, description = "Filter by capability (input_audio, voice_transcription)"), + ), + responses( + (status = 200, body = ModelsResponse), + (status = 500, description = "Internal server error"), + ), + tag = "models", +)] pub(super) async fn get_models( State(state): State>, Query(query): Query, @@ -441,6 +454,15 @@ pub(super) async fn get_models( Ok(Json(ModelsResponse { models })) } +#[utoipa::path( + post, + path = "/models/refresh", + responses( + (status = 200, body = ModelsResponse), + (status = 500, description = "Internal server error"), + ), + tag = "models", +)] pub(super) async fn refresh_models( State(state): State>, ) -> Result, StatusCode> { diff --git a/src/api/projects.rs b/src/api/projects.rs index ccfa6621a..414b0c8ad 100644 --- a/src/api/projects.rs +++ b/src/api/projects.rs @@ -48,19 +48,19 @@ fn sanitize_segment(name: &str) -> Result { // Query / request types // --------------------------------------------------------------------------- -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema, utoipa::IntoParams)] pub(super) struct AgentQuery { agent_id: String, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema, utoipa::IntoParams)] pub(super) struct ProjectListQuery { agent_id: String, #[serde(default)] status: Option, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct CreateProjectRequest { agent_id: String, name: String, @@ -78,7 +78,7 @@ pub(super) struct CreateProjectRequest { auto_discover: bool, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct UpdateProjectRequest { agent_id: String, #[serde(default)] @@ -95,7 +95,7 @@ pub(super) struct UpdateProjectRequest { status: Option, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct CreateRepoRequest { agent_id: String, name: String, @@ -108,7 +108,7 @@ pub(super) struct CreateRepoRequest { description: Option, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct CreateWorktreeRequest { agent_id: String, repo_id: String, @@ -123,40 +123,40 @@ pub(super) struct CreateWorktreeRequest { // Response types // --------------------------------------------------------------------------- -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct ProjectListResponse { projects: Vec, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct ProjectResponse { #[serde(flatten)] project: ProjectWithRelations, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct RepoResponse { repo: crate::projects::ProjectRepo, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct WorktreeResponse { worktree: crate::projects::ProjectWorktree, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct ActionResponse { success: bool, message: String, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct DiskUsageResponse { total_bytes: u64, entries: Vec, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct DiskUsageEntry { name: String, bytes: u64, @@ -310,6 +310,18 @@ async fn compute_and_cache_disk_usage( // --------------------------------------------------------------------------- /// GET /agents/projects — list projects for an agent. +#[utoipa::path( + get, + path = "/agents/projects", + params( + ProjectListQuery, + ), + responses( + (status = 200, body = ProjectListResponse), + (status = 404, description = "Agent not found"), + ), + tag = "projects", +)] pub(super) async fn list_projects( State(state): State>, Query(query): Query, @@ -331,6 +343,16 @@ pub(super) async fn list_projects( } /// POST /agents/projects — create a new project. +#[utoipa::path( + post, + path = "/agents/projects", + request_body = CreateProjectRequest, + responses( + (status = 200, body = ProjectResponse), + (status = 404, description = "Agent not found"), + ), + tag = "projects", +)] pub(super) async fn create_project( State(state): State>, Json(request): Json, @@ -414,6 +436,19 @@ pub(super) async fn create_project( } /// GET /agents/projects/{id} — get a project with repos and worktrees. +#[utoipa::path( + get, + path = "/agents/projects/{id}", + params( + ("id" = String, Path, description = "Project ID"), + AgentQuery, + ), + responses( + (status = 200, body = ProjectResponse), + (status = 404, description = "Agent or project not found"), + ), + tag = "projects", +)] pub(super) async fn get_project( State(state): State>, Path(project_id): Path, @@ -435,6 +470,19 @@ pub(super) async fn get_project( } /// PUT /agents/projects/{id} — update a project. +#[utoipa::path( + put, + path = "/agents/projects/{id}", + params( + ("id" = String, Path, description = "Project ID"), + ), + request_body = UpdateProjectRequest, + responses( + (status = 200, body = ProjectResponse), + (status = 404, description = "Agent or project not found"), + ), + tag = "projects", +)] pub(super) async fn update_project( State(state): State>, Path(project_id): Path, @@ -479,6 +527,19 @@ pub(super) async fn update_project( } /// DELETE /agents/projects/{id} — delete a project (DB records only). +#[utoipa::path( + delete, + path = "/agents/projects/{id}", + params( + ("id" = String, Path, description = "Project ID"), + AgentQuery, + ), + responses( + (status = 200, body = ActionResponse), + (status = 404, description = "Agent or project not found"), + ), + tag = "projects", +)] pub(super) async fn delete_project( State(state): State>, Path(project_id): Path, @@ -509,6 +570,19 @@ pub(super) async fn delete_project( } /// POST /agents/projects/{id}/scan — re-scan project root for repos and worktrees. +#[utoipa::path( + post, + path = "/agents/projects/{id}/scan", + params( + ("id" = String, Path, description = "Project ID"), + AgentQuery, + ), + responses( + (status = 200, body = ProjectResponse), + (status = 404, description = "Agent or project not found"), + ), + tag = "projects", +)] pub(super) async fn scan_project( State(state): State>, Path(project_id): Path, @@ -594,6 +668,19 @@ pub(super) async fn scan_project( } /// POST /agents/projects/{id}/repos — add a repo to a project. +#[utoipa::path( + post, + path = "/agents/projects/{id}/repos", + params( + ("id" = String, Path, description = "Project ID"), + ), + request_body = CreateRepoRequest, + responses( + (status = 200, body = RepoResponse), + (status = 404, description = "Agent or project not found"), + ), + tag = "projects", +)] pub(super) async fn create_repo( State(state): State>, Path(project_id): Path, @@ -635,6 +722,20 @@ pub(super) async fn create_repo( } /// DELETE /agents/projects/{project_id}/repos/{repo_id} — remove a repo. +#[utoipa::path( + delete, + path = "/agents/projects/{project_id}/repos/{repo_id}", + params( + ("project_id" = String, Path, description = "Project ID"), + ("repo_id" = String, Path, description = "Repository ID"), + AgentQuery, + ), + responses( + (status = 200, body = ActionResponse), + (status = 404, description = "Agent, project, or repo not found"), + ), + tag = "projects", +)] pub(super) async fn delete_repo( State(state): State>, Path((project_id, repo_id)): Path<(String, String)>, @@ -672,6 +773,19 @@ pub(super) async fn delete_repo( } /// POST /agents/projects/{id}/worktrees — create a worktree. +#[utoipa::path( + post, + path = "/agents/projects/{id}/worktrees", + params( + ("id" = String, Path, description = "Project ID"), + ), + request_body = CreateWorktreeRequest, + responses( + (status = 200, body = WorktreeResponse), + (status = 404, description = "Agent, project, or repo not found"), + ), + tag = "projects", +)] pub(super) async fn create_worktree( State(state): State>, Path(project_id): Path, @@ -760,6 +874,20 @@ pub(super) async fn create_worktree( } /// DELETE /agents/projects/{project_id}/worktrees/{worktree_id} — remove a worktree. +#[utoipa::path( + delete, + path = "/agents/projects/{project_id}/worktrees/{worktree_id}", + params( + ("project_id" = String, Path, description = "Project ID"), + ("worktree_id" = String, Path, description = "Worktree ID"), + AgentQuery, + ), + responses( + (status = 200, body = ActionResponse), + (status = 404, description = "Agent, project, or worktree not found"), + ), + tag = "projects", +)] pub(super) async fn delete_worktree( State(state): State>, Path((project_id, worktree_id)): Path<(String, String)>, @@ -832,6 +960,19 @@ pub(super) async fn delete_worktree( } /// GET /agents/projects/{id}/disk-usage — calculate disk usage for a project. +#[utoipa::path( + get, + path = "/agents/projects/{id}/disk-usage", + params( + ("id" = String, Path, description = "Project ID"), + AgentQuery, + ), + responses( + (status = 200, body = DiskUsageResponse), + (status = 404, description = "Agent or project not found"), + ), + tag = "projects", +)] pub(super) async fn disk_usage( State(state): State>, Path(project_id): Path, diff --git a/src/api/providers.rs b/src/api/providers.rs index f3c00b00d..3b923e9b5 100644 --- a/src/api/providers.rs +++ b/src/api/providers.rs @@ -37,7 +37,7 @@ enum DeviceOAuthSessionStatus { Failed(String), } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct ProviderStatus { anthropic: bool, openai: bool, @@ -63,33 +63,33 @@ pub(super) struct ProviderStatus { github_copilot: bool, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct ProvidersResponse { providers: ProviderStatus, has_any: bool, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct ProviderUpdateRequest { provider: String, api_key: String, model: String, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct ProviderUpdateResponse { success: bool, message: String, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct ProviderModelTestRequest { provider: String, api_key: String, model: String, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct ProviderModelTestResponse { success: bool, message: String, @@ -98,12 +98,12 @@ pub(super) struct ProviderModelTestResponse { sample: Option, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct OpenAiOAuthBrowserStartRequest { model: String, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct OpenAiOAuthBrowserStartResponse { success: bool, message: String, @@ -112,12 +112,12 @@ pub(super) struct OpenAiOAuthBrowserStartResponse { state: Option, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema, utoipa::IntoParams)] pub(super) struct OpenAiOAuthBrowserStatusRequest { state: String, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct OpenAiOAuthBrowserStatusResponse { found: bool, done: bool, @@ -343,6 +343,15 @@ async fn finalize_openai_oauth( Ok(()) } +#[utoipa::path( + get, + path = "/providers", + responses( + (status = 200, body = ProvidersResponse), + (status = 500, description = "Internal server error"), + ), + tag = "providers", +)] pub(super) async fn get_providers( State(state): State>, ) -> Result, StatusCode> { @@ -524,6 +533,16 @@ pub(super) async fn get_providers( Ok(Json(ProvidersResponse { providers, has_any })) } +#[utoipa::path( + post, + path = "/providers/openai/browser-oauth/start", + request_body = OpenAiOAuthBrowserStartRequest, + responses( + (status = 200, body = OpenAiOAuthBrowserStartResponse), + (status = 400, description = "Invalid request"), + ), + tag = "providers", +)] pub(super) async fn start_openai_browser_oauth( State(state): State>, Json(request): Json, @@ -709,6 +728,18 @@ async fn run_device_oauth_background( } } +#[utoipa::path( + get, + path = "/providers/openai/browser-oauth/status", + params( + ("state" = String, Query, description = "OAuth state parameter"), + ), + responses( + (status = 200, body = OpenAiOAuthBrowserStatusResponse), + (status = 400, description = "Invalid request"), + ), + tag = "providers", +)] pub(super) async fn openai_browser_oauth_status( Query(request): Query, ) -> Result, StatusCode> { @@ -762,6 +793,16 @@ pub(super) async fn openai_browser_oauth_status( Ok(Json(response)) } +#[utoipa::path( + post, + path = "/providers", + request_body = ProviderUpdateRequest, + responses( + (status = 200, body = ProviderUpdateResponse), + (status = 400, description = "Invalid request"), + ), + tag = "providers", +)] pub(super) async fn update_provider( State(state): State>, Json(request): Json, @@ -841,6 +882,16 @@ pub(super) async fn update_provider( })) } +#[utoipa::path( + post, + path = "/providers/test-model", + request_body = ProviderModelTestRequest, + responses( + (status = 200, body = ProviderModelTestResponse), + (status = 400, description = "Invalid request"), + ), + tag = "providers", +)] pub(super) async fn test_provider_model( Json(request): Json, ) -> Result, StatusCode> { @@ -926,6 +977,19 @@ pub(super) async fn test_provider_model( } } +#[utoipa::path( + delete, + path = "/providers/{provider}", + params( + ("provider" = String, Path, description = "Provider name to delete"), + ), + responses( + (status = 200, body = ProviderUpdateResponse), + (status = 400, description = "Invalid request"), + (status = 404, description = "Provider not found"), + ), + tag = "providers", +)] pub(super) async fn delete_provider( State(state): State>, axum::extract::Path(provider): axum::extract::Path, diff --git a/src/api/secrets.rs b/src/api/secrets.rs index 4d5d0ab6d..356593459 100644 --- a/src/api/secrets.rs +++ b/src/api/secrets.rs @@ -38,6 +38,15 @@ fn get_secrets_store( } /// `GET /api/secrets/status` — Store state, counts, encryption status. +#[utoipa::path( + get, + path = "/secrets/status", + responses( + (status = 200, body = serde_json::Value), + (status = 503, description = "Secrets store not initialized"), + ), + tag = "secrets", +)] pub async fn secrets_status(State(state): State>) -> impl IntoResponse { let store = match get_secrets_store(&state) { Ok(s) => s, @@ -57,7 +66,7 @@ pub async fn secrets_status(State(state): State>) -> impl IntoResp } } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] struct SecretListItem { name: String, category: SecretCategory, @@ -65,12 +74,21 @@ struct SecretListItem { updated_at: chrono::DateTime, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] struct SecretListResponse { secrets: Vec, } /// `GET /api/secrets` — List all secrets (name + category, no values). +#[utoipa::path( + get, + path = "/secrets", + responses( + (status = 200, body = SecretListResponse), + (status = 503, description = "Secrets store not initialized"), + ), + tag = "secrets", +)] pub async fn list_secrets(State(state): State>) -> impl IntoResponse { let store = match get_secrets_store(&state) { Ok(s) => s, @@ -99,14 +117,14 @@ pub async fn list_secrets(State(state): State>) -> impl IntoRespon } } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, utoipa::ToSchema)] pub struct PutSecretBody { pub value: String, /// If not provided, auto-categorized based on the secret name. pub category: Option, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] struct PutSecretResponse { name: String, category: SecretCategory, @@ -115,6 +133,20 @@ struct PutSecretResponse { } /// `PUT /api/secrets/:name` — Add or update a secret. +#[utoipa::path( + put, + path = "/secrets/{name}", + params( + ("name" = String, Path, description = "Secret name"), + ), + request_body = PutSecretBody, + responses( + (status = 200, body = PutSecretResponse), + (status = 423, description = "Secret store is locked"), + (status = 503, description = "Secrets store not initialized"), + ), + tag = "secrets", +)] pub async fn put_secret( State(state): State>, Path(name): Path, @@ -160,7 +192,7 @@ pub async fn put_secret( } } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] struct DeleteSecretResponse { deleted: String, #[serde(skip_serializing_if = "Option::is_none")] @@ -168,6 +200,19 @@ struct DeleteSecretResponse { } /// `DELETE /api/secrets/:name` — Delete a secret. +#[utoipa::path( + delete, + path = "/secrets/{name}", + params( + ("name" = String, Path, description = "Secret name"), + ), + responses( + (status = 200, body = DeleteSecretResponse), + (status = 423, description = "Secret store is locked"), + (status = 503, description = "Secrets store not initialized"), + ), + tag = "secrets", +)] pub async fn delete_secret( State(state): State>, Path(name): Path, @@ -199,7 +244,7 @@ pub async fn delete_secret( } } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] struct SecretInfoResponse { name: String, category: SecretCategory, @@ -208,6 +253,19 @@ struct SecretInfoResponse { } /// `GET /api/secrets/:name/info` — Secret metadata (no value). +#[utoipa::path( + get, + path = "/secrets/{name}/info", + params( + ("name" = String, Path, description = "Secret name"), + ), + responses( + (status = 200, body = SecretInfoResponse), + (status = 404, description = "Secret not found"), + (status = 503, description = "Secrets store not initialized"), + ), + tag = "secrets", +)] pub async fn secret_info( State(state): State>, Path(name): Path, @@ -233,13 +291,23 @@ pub async fn secret_info( } } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] struct EncryptResponse { master_key: String, message: String, } /// `POST /api/secrets/encrypt` — Enable encryption. Returns the master key (hex). +#[utoipa::path( + post, + path = "/secrets/encrypt", + responses( + (status = 200, body = EncryptResponse), + (status = 400, description = "Encryption already enabled"), + (status = 503, description = "Secrets store not initialized"), + ), + tag = "secrets", +)] pub async fn enable_encryption(State(state): State>) -> impl IntoResponse { let store = match get_secrets_store(&state) { Ok(s) => s, @@ -271,12 +339,24 @@ pub async fn enable_encryption(State(state): State>) -> impl IntoR } } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, utoipa::ToSchema)] pub struct UnlockBody { pub master_key: String, } /// `POST /api/secrets/unlock` — Unlock encrypted store with master key (hex). +#[utoipa::path( + post, + path = "/secrets/unlock", + request_body = UnlockBody, + responses( + (status = 200, body = serde_json::Value), + (status = 400, description = "Invalid master key format"), + (status = 401, description = "Invalid master key"), + (status = 503, description = "Secrets store not initialized"), + ), + tag = "secrets", +)] pub async fn unlock_secrets( State(state): State>, Json(body): Json, @@ -331,6 +411,16 @@ pub async fn unlock_secrets( } /// `POST /api/secrets/lock` — Lock encrypted store (clear key from memory). +#[utoipa::path( + post, + path = "/secrets/lock", + responses( + (status = 200, body = serde_json::Value), + (status = 400, description = "Lock failed"), + (status = 503, description = "Secrets store not initialized"), + ), + tag = "secrets", +)] pub async fn lock_secrets(State(state): State>) -> impl IntoResponse { let store = match get_secrets_store(&state) { Ok(s) => s, @@ -358,6 +448,16 @@ pub async fn lock_secrets(State(state): State>) -> impl IntoRespon } /// `POST /api/secrets/rotate` — Rotate master key. +#[utoipa::path( + post, + path = "/secrets/rotate", + responses( + (status = 200, body = serde_json::Value), + (status = 400, description = "Key rotation failed"), + (status = 503, description = "Secrets store not initialized"), + ), + tag = "secrets", +)] pub async fn rotate_key(State(state): State>) -> impl IntoResponse { let store = match get_secrets_store(&state) { Ok(s) => s, @@ -386,14 +486,14 @@ pub async fn rotate_key(State(state): State>) -> impl IntoResponse } } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] struct MigrationItem { config_key: String, secret_name: String, category: SecretCategory, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] struct MigrateResponse { migrated: Vec, skipped: Vec, @@ -406,6 +506,16 @@ struct MigrateResponse { /// Scans the resolved config for plaintext credential values (not `env:` or /// `secret:` prefixed) and moves them to the secret store, replacing the /// config.toml entries with `secret:NAME` references. +#[utoipa::path( + post, + path = "/secrets/migrate", + responses( + (status = 200, body = MigrateResponse), + (status = 423, description = "Secret store is locked"), + (status = 503, description = "Secrets store not initialized"), + ), + tag = "secrets", +)] pub async fn migrate_secrets(State(state): State>) -> impl IntoResponse { let store = match get_secrets_store(&state) { Ok(s) => s, @@ -489,6 +599,16 @@ pub async fn migrate_secrets(State(state): State>) -> impl IntoRes } /// `POST /api/secrets/export` — Export all secrets as a JSON backup. +#[utoipa::path( + post, + path = "/secrets/export", + responses( + (status = 200, body = serde_json::Value), + (status = 423, description = "Secret store is locked"), + (status = 503, description = "Secrets store not initialized"), + ), + tag = "secrets", +)] pub async fn export_secrets(State(state): State>) -> impl IntoResponse { let store = match get_secrets_store(&state) { Ok(s) => s, @@ -526,7 +646,7 @@ pub async fn export_secrets(State(state): State>) -> impl IntoResp } } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, utoipa::ToSchema)] pub struct ImportBody { /// The export data to import (JSON object matching ExportData format). #[serde(flatten)] @@ -537,6 +657,17 @@ pub struct ImportBody { } /// `POST /api/secrets/import` — Import secrets from a backup. +#[utoipa::path( + post, + path = "/secrets/import", + request_body = ImportBody, + responses( + (status = 200, body = serde_json::Value), + (status = 423, description = "Secret store is locked"), + (status = 503, description = "Secrets store not initialized"), + ), + tag = "secrets", +)] pub async fn import_secrets( State(state): State>, Json(body): Json, diff --git a/src/api/server.rs b/src/api/server.rs index d46152aba..1db3a7f67 100644 --- a/src/api/server.rs +++ b/src/api/server.rs @@ -8,16 +8,17 @@ use super::{ }; use axum::Json; - use axum::Router; use axum::extract::{DefaultBodyLimit, Request, State}; use axum::http::{StatusCode, Uri, header}; use axum::middleware::{self, Next}; use axum::response::{Html, IntoResponse, Response}; -use axum::routing::{any, delete, get, post, put}; +use axum::routing::any; use rust_embed::Embed; use serde_json::json; use tower_http::cors::CorsLayer; +use utoipa::OpenApi; +use utoipa_axum::{router::OpenApiRouter, routes}; use std::net::SocketAddr; use std::sync::Arc; @@ -28,6 +29,208 @@ use std::sync::Arc; #[allow(unused)] struct InterfaceAssets; +/// API documentation structure for utoipa. +#[derive(OpenApi)] +#[openapi( + info( + title = "Spacebot API", + version = env!("CARGO_PKG_VERSION"), + description = "Spacebot agent system API" + ), + servers( + (url = "/api") + ) +)] +struct ApiDoc; + +/// Build the OpenApiRouter for all API routes. +/// This is separated so it can be used both by the server and the OpenAPI spec generator. +pub fn api_router() -> OpenApiRouter> { + OpenApiRouter::with_openapi(ApiDoc::openapi()) + // System routes + .routes(routes!(system::health)) + .routes(routes!(system::idle)) + .routes(routes!(system::status)) + .routes(routes!(system::storage_status)) + .routes(routes!(system::backup_export)) + .routes(routes!(system::backup_restore)) + .routes(routes!(system::events_sse)) + // Agent routes + .routes(routes!(agents::instance_overview)) + .routes(routes!(agents::list_agents)) + .routes(routes!(agents::create_agent)) + .routes(routes!(agents::update_agent)) + .routes(routes!(agents::delete_agent)) + .routes(routes!(agents::list_agent_mcp)) + .routes(routes!(agents::reconnect_agent_mcp)) + .routes(routes!(agents::get_warmup_status)) + .routes(routes!(agents::trigger_warmup)) + .routes(routes!(agents::agent_overview)) + .routes(routes!(agents::get_agent_profile)) + .routes(routes!( + agents::get_avatar, + agents::upload_avatar, + agents::delete_avatar + )) + .routes(routes!(agents::get_identity, agents::update_identity)) + // MCP routes + .routes(routes!( + mcp::list_mcp_servers, + mcp::create_mcp_server, + mcp::update_mcp_server + )) + .routes(routes!(mcp::delete_mcp_server)) + .routes(routes!(mcp::reconnect_mcp_server)) + .routes(routes!(mcp::mcp_status)) + // Channel routes + .routes(routes!(channels::list_channels, channels::delete_channel)) + .routes(routes!(channels::set_channel_archive)) + .routes(routes!(channels::channel_messages)) + .routes(routes!(channels::channel_status)) + .routes(routes!(channels::inspect_prompt)) + .routes(routes!(channels::set_prompt_capture)) + .routes(routes!(channels::list_prompt_snapshots)) + .routes(routes!(channels::get_prompt_snapshot)) + .routes(routes!(channels::cancel_process)) + // Worker routes + .routes(routes!(workers::list_workers)) + .routes(routes!(workers::worker_detail)) + // Memory routes + .routes(routes!(memories::list_memories)) + .routes(routes!(memories::search_memories)) + .routes(routes!(memories::memory_graph)) + .routes(routes!(memories::memory_graph_neighbors)) + // Cortex routes + .routes(routes!(cortex::cortex_events)) + .routes(routes!(cortex::cortex_chat_messages)) + .routes(routes!(cortex::cortex_chat_threads)) + .routes(routes!(cortex::cortex_chat_delete_thread)) + .routes(routes!(cortex::cortex_chat_send)) + // Config routes + .routes(routes!( + config::get_agent_config, + config::update_agent_config + )) + // Cron routes + .routes(routes!( + cron::list_cron_jobs, + cron::create_or_update_cron, + cron::delete_cron + )) + .routes(routes!(cron::cron_executions)) + .routes(routes!(cron::trigger_cron)) + .routes(routes!(cron::toggle_cron)) + // Task routes + .routes(routes!(tasks::list_tasks, tasks::create_task)) + .routes(routes!( + tasks::get_task, + tasks::update_task, + tasks::delete_task + )) + .routes(routes!(tasks::approve_task)) + .routes(routes!(tasks::execute_task)) + .routes(routes!(tasks::assign_task)) + // Project routes + .routes(routes!(projects::list_projects, projects::create_project)) + .routes(routes!( + projects::get_project, + projects::update_project, + projects::delete_project + )) + .routes(routes!(projects::scan_project)) + .routes(routes!(projects::disk_usage)) + .routes(routes!(projects::create_repo)) + .routes(routes!(projects::delete_repo)) + .routes(routes!(projects::create_worktree)) + .routes(routes!(projects::delete_worktree)) + // Ingest routes + .routes(routes!( + ingest::list_ingest_files, + ingest::delete_ingest_file + )) + .routes(routes!(ingest::upload_ingest_file)) + // Skill routes + .routes(routes!(skills::list_skills)) + .routes(routes!(skills::get_skill_content)) + .routes(routes!(skills::install_skill)) + .routes(routes!(skills::upload_skill)) + .routes(routes!(skills::remove_skill)) + .routes(routes!(skills::registry_browse)) + .routes(routes!(skills::registry_search)) + .routes(routes!(skills::registry_skill_content)) + // Tool routes + .routes(routes!(tools::list_tools)) + // Secret routes + .routes(routes!(secrets::secrets_status)) + .routes(routes!(secrets::list_secrets)) + .routes(routes!(secrets::put_secret, secrets::delete_secret)) + .routes(routes!(secrets::secret_info)) + .routes(routes!(secrets::migrate_secrets)) + .routes(routes!(secrets::enable_encryption)) + .routes(routes!(secrets::unlock_secrets)) + .routes(routes!(secrets::lock_secrets)) + .routes(routes!(secrets::rotate_key)) + .routes(routes!(secrets::export_secrets)) + .routes(routes!(secrets::import_secrets)) + // Provider routes + .routes(routes!( + providers::get_providers, + providers::update_provider + )) + .routes(routes!(providers::start_openai_browser_oauth)) + .routes(routes!(providers::openai_browser_oauth_status)) + .routes(routes!(providers::test_provider_model)) + .routes(routes!(providers::delete_provider)) + // Model routes + .routes(routes!(models::get_models)) + .routes(routes!(models::refresh_models)) + // Messaging routes + .routes(routes!(messaging::messaging_status)) + .routes(routes!(messaging::disconnect_platform)) + .routes(routes!(messaging::toggle_platform)) + .routes(routes!( + messaging::create_messaging_instance, + messaging::delete_messaging_instance + )) + // Binding routes + .routes(routes!( + bindings::list_bindings, + bindings::create_binding, + bindings::update_binding, + bindings::delete_binding + )) + // Settings routes + .routes(routes!( + settings::get_global_settings, + settings::update_global_settings + )) + .routes(routes!( + settings::get_raw_config, + settings::update_raw_config + )) + .routes(routes!(settings::update_check, settings::update_check_now)) + .routes(routes!(settings::update_apply)) + .routes(routes!(settings::changelog)) + // SSH routes + .routes(routes!(ssh::set_authorized_key)) + .routes(routes!(ssh::ssh_status)) + // Webchat routes + .routes(routes!(webchat::webchat_send)) + .routes(routes!(webchat::webchat_history)) + // Link routes + .routes(routes!(links::list_links, links::create_link)) + .routes(routes!(links::update_link, links::delete_link)) + .routes(routes!(links::agent_links)) + .routes(routes!(links::topology)) + .routes(routes!(links::list_groups, links::create_group)) + .routes(routes!(links::update_group, links::delete_group)) + .routes(routes!(links::list_humans, links::create_human)) + .routes(routes!(links::update_human, links::delete_human)) + // Factory routes + .routes(routes!(factory::list_presets)) + .routes(routes!(factory::get_preset)) +} + /// Start the HTTP server on the given address. /// /// The caller provides a pre-built `ApiState` so agent event streams and @@ -52,261 +255,36 @@ pub async fn start_http_server( ]) .allow_headers([header::CONTENT_TYPE, header::AUTHORIZATION, header::ACCEPT]); - let api_routes = Router::new() - .route("/health", get(system::health)) - .route("/idle", get(system::idle)) - .route("/status", get(system::status)) - .route("/system/storage", get(system::storage_status)) - .route("/system/backup/export", get(system::backup_export)) - .route("/system/backup/restore", post(system::backup_restore)) - .route("/overview", get(agents::instance_overview)) - .route("/events", get(system::events_sse)) - .route( - "/agents", - get(agents::list_agents) - .post(agents::create_agent) - .put(agents::update_agent) - .delete(agents::delete_agent), - ) - .route("/agents/mcp", get(agents::list_agent_mcp)) - .route("/agents/mcp/reconnect", post(agents::reconnect_agent_mcp)) - .route( - "/agents/warmup", - get(agents::get_warmup_status).post(agents::trigger_warmup), - ) - .route( - "/mcp/servers", - get(mcp::list_mcp_servers) - .post(mcp::create_mcp_server) - .put(mcp::update_mcp_server), - ) - .route("/mcp/servers/{name}", delete(mcp::delete_mcp_server)) - .route( - "/mcp/servers/{name}/reconnect", - post(mcp::reconnect_mcp_server), - ) - .route("/mcp/status", get(mcp::mcp_status)) - .route("/agents/overview", get(agents::agent_overview)) - .route( - "/channels", - get(channels::list_channels).delete(channels::delete_channel), - ) - .route("/channels/archive", put(channels::set_channel_archive)) - .route("/channels/messages", get(channels::channel_messages)) - .route("/channels/status", get(channels::channel_status)) - .route("/channels/inspect", get(channels::inspect_prompt)) - .route( - "/channels/inspect/capture", - post(channels::set_prompt_capture), - ) - .route( - "/channels/inspect/snapshots", - get(channels::list_prompt_snapshots), - ) - .route( - "/channels/inspect/snapshot", - get(channels::get_prompt_snapshot), - ) - .route("/agents/workers", get(workers::list_workers)) - .route("/agents/workers/detail", get(workers::worker_detail)) + // Build the OpenAPI router and split into routes and spec + let (api_routes, api) = api_router().split_for_parts(); + + // Create protected routes (require auth) + let protected_routes = Router::new() + // API routes under /api + .nest("/api", api_routes) + // Swagger UI and OpenAPI spec (protected) + .merge(utoipa_swagger_ui::SwaggerUi::new("/api/docs").url("/api/openapi.json", api)) + // Opencode proxy routes (protected) .route( - "/opencode/{port}/{*path}", + "/api/opencode/{port}/{*path}", any(opencode_proxy::opencode_proxy), ) - .route("/opencode/{port}", any(opencode_proxy::opencode_proxy)) - .route("/opencode/{port}/", any(opencode_proxy::opencode_proxy)) - .route("/agents/memories", get(memories::list_memories)) - .route("/agents/memories/search", get(memories::search_memories)) - .route("/agents/memories/graph", get(memories::memory_graph)) - .route( - "/agents/memories/graph/neighbors", - get(memories::memory_graph_neighbors), - ) - .route("/cortex/events", get(cortex::cortex_events)) - .route("/cortex-chat/messages", get(cortex::cortex_chat_messages)) - .route("/cortex-chat/threads", get(cortex::cortex_chat_threads)) - .route( - "/cortex-chat/thread", - delete(cortex::cortex_chat_delete_thread), - ) - .route("/cortex-chat/send", post(cortex::cortex_chat_send)) - .route("/agents/profile", get(agents::get_agent_profile)) - .route( - "/agents/avatar", - get(agents::get_avatar) - .post(agents::upload_avatar) - .delete(agents::delete_avatar), - ) - .route( - "/agents/identity", - get(agents::get_identity).put(agents::update_identity), - ) - .route( - "/agents/config", - get(config::get_agent_config).put(config::update_agent_config), - ) - .route( - "/agents/cron", - get(cron::list_cron_jobs) - .post(cron::create_or_update_cron) - .delete(cron::delete_cron), - ) - .route("/agents/cron/executions", get(cron::cron_executions)) - .route("/agents/cron/trigger", post(cron::trigger_cron)) - .route("/agents/cron/toggle", put(cron::toggle_cron)) - .route("/tasks", get(tasks::list_tasks).post(tasks::create_task)) - .route( - "/tasks/{number}", - get(tasks::get_task) - .put(tasks::update_task) - .delete(tasks::delete_task), - ) - .route("/tasks/{number}/approve", post(tasks::approve_task)) - .route("/tasks/{number}/execute", post(tasks::execute_task)) - .route("/tasks/{number}/assign", post(tasks::assign_task)) - .route( - "/agents/projects", - get(projects::list_projects).post(projects::create_project), - ) - .route( - "/agents/projects/{id}", - get(projects::get_project) - .put(projects::update_project) - .delete(projects::delete_project), - ) - .route("/agents/projects/{id}/scan", post(projects::scan_project)) - .route( - "/agents/projects/{id}/disk-usage", - get(projects::disk_usage), - ) - .route("/agents/projects/{id}/repos", post(projects::create_repo)) - .route( - "/agents/projects/{id}/repos/{repo_id}", - delete(projects::delete_repo), - ) - .route( - "/agents/projects/{id}/worktrees", - post(projects::create_worktree), - ) - .route( - "/agents/projects/{id}/worktrees/{wt_id}", - delete(projects::delete_worktree), - ) - .route("/channels/cancel", post(channels::cancel_process)) - .route( - "/agents/ingest/files", - get(ingest::list_ingest_files).delete(ingest::delete_ingest_file), - ) - .route("/agents/ingest/upload", post(ingest::upload_ingest_file)) - .route("/agents/skills", get(skills::list_skills)) - .route("/agents/skills/content", get(skills::get_skill_content)) - .route("/agents/skills/install", post(skills::install_skill)) - .route("/agents/skills/upload", post(skills::upload_skill)) - .route("/agents/skills/remove", delete(skills::remove_skill)) - .route("/agents/tools", get(tools::list_tools)) - // Secret store management - .route("/secrets/status", get(secrets::secrets_status)) - .route("/secrets", get(secrets::list_secrets)) - .route( - "/secrets/{name}", - put(secrets::put_secret).delete(secrets::delete_secret), - ) - .route("/secrets/{name}/info", get(secrets::secret_info)) - .route("/secrets/migrate", post(secrets::migrate_secrets)) - .route("/secrets/encrypt", post(secrets::enable_encryption)) - .route("/secrets/unlock", post(secrets::unlock_secrets)) - .route("/secrets/lock", post(secrets::lock_secrets)) - .route("/secrets/rotate", post(secrets::rotate_key)) - .route("/secrets/export", post(secrets::export_secrets)) - .route("/secrets/import", post(secrets::import_secrets)) - .route("/skills/registry/browse", get(skills::registry_browse)) - .route("/skills/registry/search", get(skills::registry_search)) - .route( - "/skills/registry/content", - get(skills::registry_skill_content), - ) - .route( - "/providers", - get(providers::get_providers).put(providers::update_provider), - ) - .route( - "/providers/openai/oauth/browser/start", - post(providers::start_openai_browser_oauth), - ) - .route( - "/providers/openai/oauth/browser/status", - get(providers::openai_browser_oauth_status), - ) - .route("/providers/test", post(providers::test_provider_model)) - .route("/providers/{provider}", delete(providers::delete_provider)) - .route("/models", get(models::get_models)) - .route("/models/refresh", post(models::refresh_models)) - .route("/messaging/status", get(messaging::messaging_status)) - .route( - "/messaging/disconnect", - post(messaging::disconnect_platform), - ) - .route("/messaging/toggle", post(messaging::toggle_platform)) - .route( - "/messaging/instances", - post(messaging::create_messaging_instance).delete(messaging::delete_messaging_instance), - ) - .route( - "/bindings", - get(bindings::list_bindings) - .post(bindings::create_binding) - .put(bindings::update_binding) - .delete(bindings::delete_binding), - ) - .route( - "/settings", - get(settings::get_global_settings).put(settings::update_global_settings), - ) - .route( - "/config/raw", - get(settings::get_raw_config).put(settings::update_raw_config), - ) - .route( - "/update/check", - get(settings::update_check).post(settings::update_check_now), - ) - .route("/update/apply", post(settings::update_apply)) - .route("/changelog", get(settings::changelog)) - .route("/ssh/authorized-key", put(ssh::set_authorized_key)) - .route("/ssh/status", get(ssh::ssh_status)) - .route("/webchat/send", post(webchat::webchat_send)) - .route("/webchat/history", get(webchat::webchat_history)) - .route("/links", get(links::list_links).post(links::create_link)) - .route( - "/links/{from}/{to}", - put(links::update_link).delete(links::delete_link), - ) - .route("/agents/{id}/links", get(links::agent_links)) - .route("/topology", get(links::topology)) - .route("/groups", get(links::list_groups).post(links::create_group)) - .route( - "/groups/{name}", - put(links::update_group).delete(links::delete_group), - ) - .route("/humans", get(links::list_humans).post(links::create_human)) - .route( - "/humans/{id}", - put(links::update_human).delete(links::delete_human), - ) - // Factory: preset archetypes - .route("/factory/presets", get(factory::list_presets)) - .route("/factory/presets/{id}", get(factory::get_preset)) - .layer(DefaultBodyLimit::max(10 * 1024 * 1024)) + .route("/api/opencode/{port}", any(opencode_proxy::opencode_proxy)) + .route("/api/opencode/{port}/", any(opencode_proxy::opencode_proxy)) + // Apply auth middleware to all protected routes .layer(middleware::from_fn_with_state( state.clone(), api_auth_middleware, )); #[cfg(feature = "metrics")] - let api_routes = api_routes.layer(middleware::from_fn(metrics_middleware)); + let protected_routes = protected_routes.layer(middleware::from_fn(metrics_middleware)); + // Build the main application router let app = Router::new() - .nest("/api", api_routes) + // Mount all protected routes + .merge(protected_routes) + // Static file handler for frontend (unprotected) .fallback(static_handler) .layer(cors) .layer(DefaultBodyLimit::max(10 * 1024 * 1024)) // 10 MiB diff --git a/src/api/settings.rs b/src/api/settings.rs index 52eaf44a4..5c401bb5b 100644 --- a/src/api/settings.rs +++ b/src/api/settings.rs @@ -6,7 +6,7 @@ use axum::http::StatusCode; use serde::{Deserialize, Serialize}; use std::sync::Arc; -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct GlobalSettingsResponse { brave_search_key: Option, api_enabled: bool, @@ -17,7 +17,7 @@ pub(super) struct GlobalSettingsResponse { ssh_enabled: bool, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct OpenCodeSettingsResponse { enabled: bool, path: String, @@ -27,14 +27,14 @@ pub(super) struct OpenCodeSettingsResponse { permissions: OpenCodePermissionsResponse, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct OpenCodePermissionsResponse { edit: String, bash: String, webfetch: String, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct GlobalSettingsUpdate { brave_search_key: Option, api_enabled: Option, @@ -45,7 +45,7 @@ pub(super) struct GlobalSettingsUpdate { ssh_enabled: Option, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct OpenCodeSettingsUpdate { enabled: Option, path: Option, @@ -55,36 +55,45 @@ pub(super) struct OpenCodeSettingsUpdate { permissions: Option, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct OpenCodePermissionsUpdate { edit: Option, bash: Option, webfetch: Option, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct GlobalSettingsUpdateResponse { success: bool, message: String, requires_restart: bool, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct RawConfigResponse { content: String, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct RawConfigUpdateRequest { content: String, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct RawConfigUpdateResponse { success: bool, message: String, } +#[utoipa::path( + get, + path = "/settings", + responses( + (status = 200, body = GlobalSettingsResponse), + (status = 500, description = "Internal server error"), + ), + tag = "settings", +)] pub(super) async fn get_global_settings( State(state): State>, ) -> Result, StatusCode> { @@ -233,6 +242,16 @@ pub(super) async fn get_global_settings( })) } +#[utoipa::path( + put, + path = "/settings", + request_body = GlobalSettingsUpdate, + responses( + (status = 200, body = GlobalSettingsUpdateResponse), + (status = 500, description = "Internal server error"), + ), + tag = "settings", +)] pub(super) async fn update_global_settings( State(state): State>, Json(request): Json, @@ -431,6 +450,14 @@ pub(super) async fn update_global_settings( } /// Return the embedded CHANGELOG.md content. +#[utoipa::path( + get, + path = "/changelog", + responses( + (status = 200, body = serde_json::Value), + ), + tag = "settings", +)] pub(super) async fn changelog() -> Json { Json(serde_json::json!({ "content": crate::self_awareness::changelog_content(), @@ -438,6 +465,14 @@ pub(super) async fn changelog() -> Json { } /// Return the current update status (from background check). +#[utoipa::path( + get, + path = "/update-check", + responses( + (status = 200, body = crate::update::UpdateStatus), + ), + tag = "settings", +)] pub(super) async fn update_check( State(state): State>, ) -> Json { @@ -446,6 +481,14 @@ pub(super) async fn update_check( } /// Force an immediate update check against GitHub. +#[utoipa::path( + post, + path = "/update-check", + responses( + (status = 200, body = crate::update::UpdateStatus), + ), + tag = "settings", +)] pub(super) async fn update_check_now( State(state): State>, ) -> Json { @@ -455,6 +498,15 @@ pub(super) async fn update_check_now( } /// Pull the new Docker image and recreate this container. +#[utoipa::path( + post, + path = "/update-apply", + responses( + (status = 200, body = serde_json::Value), + (status = 500, description = "Internal server error"), + ), + tag = "settings", +)] pub(super) async fn update_apply( State(state): State>, ) -> Result, StatusCode> { @@ -470,6 +522,15 @@ pub(super) async fn update_apply( } } +#[utoipa::path( + get, + path = "/settings/raw", + responses( + (status = 200, body = RawConfigResponse), + (status = 500, description = "Internal server error"), + ), + tag = "settings", +)] pub(super) async fn get_raw_config( State(state): State>, ) -> Result, StatusCode> { @@ -493,6 +554,17 @@ pub(super) async fn get_raw_config( Ok(Json(RawConfigResponse { content })) } +#[utoipa::path( + put, + path = "/settings/raw", + request_body = RawConfigUpdateRequest, + responses( + (status = 200, body = RawConfigUpdateResponse), + (status = 400, description = "Validation error"), + (status = 500, description = "Internal server error"), + ), + tag = "settings", +)] pub(super) async fn update_raw_config( State(state): State>, Json(request): Json, diff --git a/src/api/skills.rs b/src/api/skills.rs index 506216d18..19ece5398 100644 --- a/src/api/skills.rs +++ b/src/api/skills.rs @@ -30,7 +30,7 @@ static REGISTRY_SKILL_CONTENT_CACHE: LazyLock>> = .build() }); -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct SkillInfo { name: String, description: String, @@ -41,12 +41,12 @@ pub(super) struct SkillInfo { source_repo: Option, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct SkillsListResponse { skills: Vec, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct InstallSkillRequest { agent_id: String, spec: String, @@ -54,24 +54,24 @@ pub(super) struct InstallSkillRequest { instance: bool, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct InstallSkillResponse { installed: Vec, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct RemoveSkillRequest { agent_id: String, name: String, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct RemoveSkillResponse { success: bool, path: Option, } -#[derive(Serialize, Deserialize, Clone)] +#[derive(Serialize, Deserialize, Clone, utoipa::ToSchema)] pub(super) struct RegistrySkill { source: String, #[serde(rename = "skillId")] @@ -85,7 +85,7 @@ pub(super) struct RegistrySkill { id: Option, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct RegistryBrowseResponse { skills: Vec, has_more: bool, @@ -93,20 +93,20 @@ pub(super) struct RegistryBrowseResponse { total: Option, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct RegistrySearchResponse { skills: Vec, query: String, count: usize, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema, utoipa::IntoParams)] pub(super) struct SkillContentQuery { agent_id: String, name: String, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct SkillContentResponse { name: String, description: String, @@ -118,17 +118,17 @@ pub(super) struct SkillContentResponse { source_repo: Option, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct UploadSkillResponse { installed: Vec, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema, utoipa::IntoParams)] pub(super) struct SkillsQuery { agent_id: String, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema, utoipa::IntoParams)] pub(super) struct RegistryBrowseQuery { #[serde(default = "default_registry_view")] view: String, @@ -140,7 +140,7 @@ fn default_registry_view() -> String { "all-time".into() } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema, utoipa::IntoParams)] pub(super) struct RegistrySearchQuery { q: String, #[serde(default = "default_registry_search_limit")] @@ -151,7 +151,7 @@ fn default_registry_search_limit() -> u32 { 50 } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema, utoipa::IntoParams)] pub(super) struct RegistrySkillContentQuery { /// GitHub `owner/repo`. source: String, @@ -159,7 +159,7 @@ pub(super) struct RegistrySkillContentQuery { skill_id: String, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct RegistrySkillContentResponse { source: String, skill_id: String, @@ -167,21 +167,36 @@ pub(super) struct RegistrySkillContentResponse { } /// List installed skills for an agent. +#[utoipa::path( + get, + path = "/agents/skills", + params( + ("agent_id" = String, Query, description = "Agent ID"), + ), + responses( + (status = 200, body = SkillsListResponse), + (status = 404, description = "Agent not found"), + ), + tag = "skills", +)] pub(super) async fn list_skills( State(state): State>, Query(query): Query, ) -> Result, StatusCode> { - let configs = state.agent_configs.load(); - let agent = configs - .iter() - .find(|a| a.id == query.agent_id) - .ok_or(StatusCode::NOT_FOUND)?; - - let instance_dir = state.instance_dir.load(); - let instance_skills_dir = instance_dir.join("skills"); - let workspace_skills_dir = agent.workspace.join("skills"); - - let skills = crate::skills::SkillSet::load(&instance_skills_dir, &workspace_skills_dir).await; + let skills: crate::skills::SkillSet; + { + let configs = state.agent_configs.load(); + let agent = configs + .iter() + .find(|a| a.id == query.agent_id) + .ok_or(StatusCode::NOT_FOUND)?; + + let instance_dir = state.instance_dir.load(); + let instance_skills_dir = instance_dir.join("skills"); + let workspace_skills_dir = std::path::PathBuf::from(&agent.workspace).join("skills"); + + skills = crate::skills::SkillSet::load(&instance_skills_dir, &workspace_skills_dir).await; + } let skill_infos: Vec = skills .list() @@ -205,6 +220,17 @@ pub(super) async fn list_skills( } /// Install a skill from GitHub. +#[utoipa::path( + post, + path = "/agents/skills/install", + request_body = InstallSkillRequest, + responses( + (status = 200, body = InstallSkillResponse), + (status = 404, description = "Agent not found"), + (status = 500, description = "Internal server error"), + ), + tag = "skills", +)] pub(super) async fn install_skill( State(state): State>, axum::extract::Json(req): axum::extract::Json, @@ -218,7 +244,7 @@ pub(super) async fn install_skill( let target_dir = if req.instance { state.instance_dir.load().as_ref().join("skills") } else { - agent.workspace.join("skills") + std::path::PathBuf::from(&agent.workspace).join("skills") }; let installed = crate::skills::install_from_github(&req.spec, &target_dir) @@ -234,6 +260,18 @@ pub(super) async fn install_skill( } /// Remove an installed skill. +#[utoipa::path( + delete, + path = "/agents/skills/remove", + request_body = RemoveSkillRequest, + responses( + (status = 200, body = RemoveSkillResponse), + (status = 403, description = "Cannot remove instance-level skill"), + (status = 404, description = "Agent not found"), + (status = 500, description = "Internal server error"), + ), + tag = "skills", +)] pub(super) async fn remove_skill( State(state): State>, axum::extract::Json(req): axum::extract::Json, @@ -246,7 +284,7 @@ pub(super) async fn remove_skill( let instance_dir = state.instance_dir.load(); let instance_skills_dir = instance_dir.join("skills"); - let workspace_skills_dir = agent.workspace.join("skills"); + let workspace_skills_dir = std::path::PathBuf::from(&agent.workspace).join("skills"); let mut skills = crate::skills::SkillSet::load(&instance_skills_dir, &workspace_skills_dir).await; @@ -277,6 +315,19 @@ pub(super) async fn remove_skill( } /// Get the full content of an installed skill. +#[utoipa::path( + get, + path = "/agents/skills/content", + params( + ("agent_id" = String, Query, description = "Agent ID"), + ("name" = String, Query, description = "Skill name"), + ), + responses( + (status = 200, body = SkillContentResponse), + (status = 404, description = "Agent or skill not found"), + ), + tag = "skills", +)] pub(super) async fn get_skill_content( State(state): State>, Query(query): Query, @@ -289,7 +340,7 @@ pub(super) async fn get_skill_content( let instance_dir = state.instance_dir.load(); let instance_skills_dir = instance_dir.join("skills"); - let workspace_skills_dir = agent.workspace.join("skills"); + let workspace_skills_dir = std::path::PathBuf::from(&agent.workspace).join("skills"); let skills = crate::skills::SkillSet::load(&instance_skills_dir, &workspace_skills_dir).await; @@ -310,6 +361,20 @@ pub(super) async fn get_skill_content( } /// Upload skill files (zip archives or directories) from the user's computer. +#[utoipa::path( + post, + path = "/agents/skills/upload", + params( + ("agent_id" = String, Query, description = "Agent ID"), + ), + responses( + (status = 200, body = UploadSkillResponse), + (status = 400, description = "Invalid request"), + (status = 404, description = "Agent not found"), + (status = 500, description = "Internal server error"), + ), + tag = "skills", +)] pub(super) async fn upload_skill( State(state): State>, Query(query): Query, @@ -321,7 +386,7 @@ pub(super) async fn upload_skill( .find(|a| a.id == query.agent_id) .ok_or(StatusCode::NOT_FOUND)?; - let target_dir = agent.workspace.join("skills"); + let target_dir = std::path::PathBuf::from(&agent.workspace).join("skills"); tokio::fs::create_dir_all(&target_dir) .await @@ -402,6 +467,19 @@ pub(super) async fn upload_skill( } /// Proxy browse requests to skills.sh leaderboard API. +#[utoipa::path( + get, + path = "/skills/registry/browse", + params( + ("view" = String, Query, description = "View type (all-time, trending, hot)"), + ("page" = u32, Query, description = "Page number"), + ), + responses( + (status = 200, body = RegistryBrowseResponse), + (status = 502, description = "Bad gateway"), + ), + tag = "skills", +)] pub(super) async fn registry_browse( Query(query): Query, ) -> Result, StatusCode> { @@ -428,7 +506,7 @@ pub(super) async fn registry_browse( return Err(StatusCode::BAD_GATEWAY); } - #[derive(Deserialize)] + #[derive(Deserialize, utoipa::ToSchema)] struct UpstreamResponse { skills: Vec, #[serde(default)] @@ -455,6 +533,20 @@ pub(super) async fn registry_browse( } /// Proxy search requests to skills.sh search API. +#[utoipa::path( + get, + path = "/skills/registry/search", + params( + ("q" = String, Query, description = "Search query"), + ("limit" = u32, Query, description = "Result limit"), + ), + responses( + (status = 200, body = RegistrySearchResponse), + (status = 400, description = "Invalid request"), + (status = 502, description = "Bad gateway"), + ), + tag = "skills", +)] pub(super) async fn registry_search( Query(query): Query, ) -> Result, StatusCode> { @@ -482,7 +574,7 @@ pub(super) async fn registry_search( return Err(StatusCode::BAD_GATEWAY); } - #[derive(Deserialize)] + #[derive(Deserialize, utoipa::ToSchema)] struct UpstreamSearchResponse { skills: Vec, count: usize, @@ -506,6 +598,19 @@ pub(super) async fn registry_search( } /// Fetch the full SKILL.md content for a registry skill from GitHub. +#[utoipa::path( + get, + path = "/skills/registry/content", + params( + ("source" = String, Query, description = "GitHub owner/repo"), + ("skill_id" = String, Query, description = "Skill identifier within the repo"), + ), + responses( + (status = 200, body = RegistrySkillContentResponse), + (status = 400, description = "Invalid request"), + ), + tag = "skills", +)] pub(super) async fn registry_skill_content( Query(query): Query, ) -> Result, StatusCode> { diff --git a/src/api/ssh.rs b/src/api/ssh.rs index ff83ab898..7d139563f 100644 --- a/src/api/ssh.rs +++ b/src/api/ssh.rs @@ -27,18 +27,18 @@ const HOST_KEY_TYPES: &[&str] = &["rsa", "ecdsa", "ed25519"]; /// Path to the sshd PID file used for lifecycle management. const PID_FILE: &str = "/run/spacebot-sshd.pid"; -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct AuthorizedKeyRequest { public_key: String, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct AuthorizedKeyResponse { success: bool, message: String, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct SshStatusResponse { enabled: bool, port: u16, @@ -46,6 +46,17 @@ pub(super) struct SshStatusResponse { } /// PUT /api/ssh/authorized-key — write the public key for SSH access. +#[utoipa::path( + put, + path = "/ssh/authorized-key", + request_body = AuthorizedKeyRequest, + responses( + (status = 200, body = AuthorizedKeyResponse), + (status = 400, description = "Invalid request"), + (status = 500, description = "Internal server error"), + ), + tag = "ssh", +)] pub(super) async fn set_authorized_key( State(state): State>, Json(request): Json, @@ -90,6 +101,16 @@ pub(super) async fn set_authorized_key( } /// GET /api/ssh/status — check if SSH is available. +#[utoipa::path( + get, + path = "/ssh/status", + responses( + (status = 200, body = SshStatusResponse), + (status = 400, description = "Invalid request"), + (status = 500, description = "Internal server error"), + ), + tag = "ssh", +)] pub(super) async fn ssh_status( State(state): State>, ) -> Result, StatusCode> { diff --git a/src/api/state.rs b/src/api/state.rs index 837503d8f..356671886 100644 --- a/src/api/state.rs +++ b/src/api/state.rs @@ -29,7 +29,7 @@ use std::time::Instant; use tokio::sync::{RwLock, broadcast, mpsc}; /// Summary of an agent's configuration, exposed via the API. -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, Serialize, utoipa::ToSchema)] pub struct AgentInfo { pub id: String, #[serde(skip_serializing_if = "Option::is_none")] @@ -40,7 +40,7 @@ pub struct AgentInfo { pub gradient_start: Option, #[serde(skip_serializing_if = "Option::is_none")] pub gradient_end: Option, - pub workspace: PathBuf, + pub workspace: String, pub context_window: usize, pub max_turns: usize, pub max_concurrent_branches: usize, @@ -143,7 +143,7 @@ pub struct ApiState { } /// Events sent to SSE clients. Wraps ProcessEvents with agent context. -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, Serialize, utoipa::ToSchema)] #[serde(tag = "type", rename_all = "snake_case")] pub enum ApiEvent { /// An inbound message from a user. diff --git a/src/api/system.rs b/src/api/system.rs index b436bafc2..3b49e261c 100644 --- a/src/api/system.rs +++ b/src/api/system.rs @@ -16,19 +16,19 @@ use std::sync::Arc; use zip::CompressionMethod; use zip::write::SimpleFileOptions; -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct HealthResponse { status: &'static str, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct IdleResponse { idle: bool, active_workers: usize, active_branches: usize, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct StatusResponse { status: &'static str, version: &'static str, @@ -36,12 +36,28 @@ pub(super) struct StatusResponse { uptime_seconds: u64, } +#[utoipa::path( + get, + path = "/health", + responses( + (status = 200, body = HealthResponse), + ), + tag = "system", +)] pub(super) async fn health() -> Json { Json(HealthResponse { status: "ok" }) } /// Reports whether the instance is idle (no active workers or branches). /// Used by the platform to gate rolling updates. +#[utoipa::path( + get, + path = "/idle", + responses( + (status = 200, body = IdleResponse), + ), + tag = "system", +)] pub(super) async fn idle(State(state): State>) -> Json { let blocks = state.channel_status_blocks.read().await; let mut total_workers = 0; @@ -60,6 +76,14 @@ pub(super) async fn idle(State(state): State>) -> Json>) -> Json { let uptime = state.started_at.elapsed(); Json(StatusResponse { @@ -71,6 +95,14 @@ pub(super) async fn status(State(state): State>) -> Json>, ) -> Sse>> { @@ -132,13 +164,22 @@ pub(super) async fn events_sse( ) } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub struct StorageStatus { used_bytes: u64, total_bytes: u64, available_bytes: u64, } +#[utoipa::path( + get, + path = "/system/storage", + responses( + (status = 200, body = StorageStatus), + (status = 503, description = "No runtime config available"), + ), + tag = "system", +)] pub(super) async fn storage_status( State(state): State>, ) -> Result, (axum::http::StatusCode, String)> { @@ -227,6 +268,15 @@ fn directory_size_bytes(root: &Path) -> anyhow::Result { Ok(total) } +#[utoipa::path( + get, + path = "/system/backup/export", + responses( + (status = 200, description = "Backup archive", content_type = "application/zip"), + (status = 503, description = "No runtime config available"), + ), + tag = "system", +)] pub(super) async fn backup_export( State(state): State>, ) -> Result { @@ -267,6 +317,17 @@ pub(super) async fn backup_export( Ok((headers, archive_bytes)) } +#[utoipa::path( + post, + path = "/system/backup/restore", + request_body = Vec, + responses( + (status = 200, description = "Backup restored successfully"), + (status = 400, description = "Empty payload"), + (status = 503, description = "No runtime config available"), + ), + tag = "system", +)] pub(super) async fn backup_restore( State(state): State>, body: Bytes, diff --git a/src/api/tasks.rs b/src/api/tasks.rs index 9ae60211f..5181c7d14 100644 --- a/src/api/tasks.rs +++ b/src/api/tasks.rs @@ -10,7 +10,7 @@ use std::sync::Arc; // Request / response types // --------------------------------------------------------------------------- -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema, utoipa::IntoParams)] pub(super) struct TaskListQuery { /// Convenience filter: matches tasks where owner OR assigned equals this value. #[serde(default)] @@ -31,7 +31,7 @@ pub(super) struct TaskListQuery { limit: i64, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct CreateTaskRequest { /// Agent that owns (created) this task. owner_agent_id: String, @@ -55,7 +55,7 @@ pub(super) struct CreateTaskRequest { created_by: Option, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct UpdateTaskRequest { #[serde(default)] title: Option, @@ -79,28 +79,28 @@ pub(super) struct UpdateTaskRequest { approved_by: Option, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct ApproveRequest { #[serde(default)] approved_by: Option, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct AssignRequest { assigned_agent_id: String, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct TaskListResponse { tasks: Vec, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct TaskResponse { task: crate::tasks::Task, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct TaskActionResponse { success: bool, message: String, @@ -159,6 +159,16 @@ fn emit_task_event(state: &ApiState, task: &crate::tasks::Task, action: &str) { // --------------------------------------------------------------------------- /// `GET /tasks` — list tasks with optional filters. +#[utoipa::path( + get, + path = "/tasks", + params(TaskListQuery), + responses( + (status = 200, body = TaskListResponse), + (status = 503, description = "Task store not initialized"), + ), + tag = "tasks", +)] pub(super) async fn list_tasks( State(state): State>, Query(query): Query, @@ -188,6 +198,19 @@ pub(super) async fn list_tasks( } /// `GET /tasks/{number}` — get a task by globally unique number. +#[utoipa::path( + get, + path = "/tasks/{number}", + params( + ("number" = i64, Path, description = "Task number"), + ), + responses( + (status = 200, body = TaskResponse), + (status = 404, description = "Task not found"), + (status = 503, description = "Task store not initialized"), + ), + tag = "tasks", +)] pub(super) async fn get_task( State(state): State>, Path(number): Path, @@ -207,6 +230,17 @@ pub(super) async fn get_task( } /// `POST /tasks` — create a task. +#[utoipa::path( + post, + path = "/tasks", + request_body = CreateTaskRequest, + responses( + (status = 200, body = TaskResponse), + (status = 400, description = "Invalid request"), + (status = 503, description = "Task store not initialized"), + ), + tag = "tasks", +)] pub(super) async fn create_task( State(state): State>, Json(request): Json, @@ -246,6 +280,21 @@ pub(super) async fn create_task( } /// `PUT /tasks/{number}` — update a task. +#[utoipa::path( + put, + path = "/tasks/{number}", + params( + ("number" = i64, Path, description = "Task number"), + ), + request_body = UpdateTaskRequest, + responses( + (status = 200, body = TaskResponse), + (status = 400, description = "Invalid request"), + (status = 404, description = "Task not found"), + (status = 503, description = "Task store not initialized"), + ), + tag = "tasks", +)] pub(super) async fn update_task( State(state): State>, Path(number): Path, @@ -285,6 +334,19 @@ pub(super) async fn update_task( } /// `DELETE /tasks/{number}` — delete a task. +#[utoipa::path( + delete, + path = "/tasks/{number}", + params( + ("number" = i64, Path, description = "Task number"), + ), + responses( + (status = 200, body = TaskActionResponse), + (status = 404, description = "Task not found"), + (status = 503, description = "Task store not initialized"), + ), + tag = "tasks", +)] pub(super) async fn delete_task( State(state): State>, Path(number): Path, @@ -327,6 +389,20 @@ pub(super) async fn delete_task( } /// `POST /tasks/{number}/approve` — approve a task (move to ready). +#[utoipa::path( + post, + path = "/tasks/{number}/approve", + params( + ("number" = i64, Path, description = "Task number"), + ), + request_body = ApproveRequest, + responses( + (status = 200, body = TaskResponse), + (status = 404, description = "Task not found"), + (status = 503, description = "Task store not initialized"), + ), + tag = "tasks", +)] pub(super) async fn approve_task( State(state): State>, Path(number): Path, @@ -356,6 +432,21 @@ pub(super) async fn approve_task( /// `POST /tasks/{number}/execute` — move a task to ready for execution. /// Tasks already in `ready` or `in_progress` are returned as-is. +#[utoipa::path( + post, + path = "/tasks/{number}/execute", + params( + ("number" = i64, Path, description = "Task number"), + ), + request_body = ApproveRequest, + responses( + (status = 200, body = TaskResponse), + (status = 404, description = "Task not found"), + (status = 409, description = "Task pending approval"), + (status = 503, description = "Task store not initialized"), + ), + tag = "tasks", +)] pub(super) async fn execute_task( State(state): State>, Path(number): Path, @@ -405,6 +496,20 @@ pub(super) async fn execute_task( } /// `POST /tasks/{number}/assign` — reassign a task to a different agent. +#[utoipa::path( + post, + path = "/tasks/{number}/assign", + params( + ("number" = i64, Path, description = "Task number"), + ), + request_body = AssignRequest, + responses( + (status = 200, body = TaskResponse), + (status = 404, description = "Task not found"), + (status = 503, description = "Task store not initialized"), + ), + tag = "tasks", +)] pub(super) async fn assign_task( State(state): State>, Path(number): Path, diff --git a/src/api/tools.rs b/src/api/tools.rs index 401e2e432..7b7ab3e61 100644 --- a/src/api/tools.rs +++ b/src/api/tools.rs @@ -9,13 +9,13 @@ use serde::Serialize; use std::io::ErrorKind; use std::sync::Arc; -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct ToolsResponse { tools_bin: String, binaries: Vec, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct BinaryEntry { name: String, size: u64, @@ -23,6 +23,15 @@ pub(super) struct BinaryEntry { } /// List the contents of the durable `tools/bin` directory. +#[utoipa::path( + get, + path = "/tools", + responses( + (status = 200, body = ToolsResponse), + (status = 500, description = "Internal server error"), + ), + tag = "tools", +)] pub(super) async fn list_tools( State(state): State>, ) -> Result, StatusCode> { diff --git a/src/api/webchat.rs b/src/api/webchat.rs index 68d8ec10d..8a7c4873a 100644 --- a/src/api/webchat.rs +++ b/src/api/webchat.rs @@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::sync::Arc; -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema)] pub(super) struct WebChatSendRequest { agent_id: String, session_id: String, @@ -21,13 +21,24 @@ fn default_sender_name() -> String { "user".into() } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct WebChatSendResponse { ok: bool, } /// Fire-and-forget message injection. The response arrives via the global SSE /// event bus (`/api/events`), same as every other channel. +#[utoipa::path( + post, + path = "/webchat/send", + request_body = WebChatSendRequest, + responses( + (status = 200, body = WebChatSendResponse), + (status = 400, description = "Invalid request"), + (status = 503, description = "Messaging manager not available"), + ), + tag = "webchat", +)] pub(super) async fn webchat_send( State(state): State>, axum::Json(request): axum::Json, @@ -68,7 +79,7 @@ pub(super) async fn webchat_send( Ok(Json(WebChatSendResponse { ok: true })) } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema, utoipa::IntoParams)] pub(super) struct WebChatHistoryQuery { agent_id: String, session_id: String, @@ -80,13 +91,28 @@ fn default_limit() -> i64 { 100 } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct WebChatHistoryMessage { id: String, role: String, content: String, } +#[utoipa::path( + get, + path = "/webchat/history", + params( + ("agent_id" = String, Query, description = "Agent ID"), + ("session_id" = String, Query, description = "Session ID"), + ("limit" = i64, Query, description = "Maximum number of messages to return (default: 100, max: 200)"), + ), + responses( + (status = 200, body = Vec), + (status = 404, description = "Agent not found"), + (status = 500, description = "Internal server error"), + ), + tag = "webchat", +)] pub(super) async fn webchat_history( State(state): State>, Query(query): Query, diff --git a/src/api/workers.rs b/src/api/workers.rs index 742bfbd93..20c531004 100644 --- a/src/api/workers.rs +++ b/src/api/workers.rs @@ -11,7 +11,7 @@ use axum::http::StatusCode; use serde::{Deserialize, Serialize}; use std::sync::Arc; -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema, utoipa::IntoParams)] pub(super) struct WorkerListQuery { agent_id: String, #[serde(default = "default_limit")] @@ -25,13 +25,13 @@ fn default_limit() -> i64 { 50 } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct WorkerListResponse { workers: Vec, total: i64, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct WorkerListItem { id: String, task: String, @@ -60,13 +60,13 @@ pub(super) struct WorkerListItem { project_name: Option, } -#[derive(Deserialize)] +#[derive(Deserialize, utoipa::ToSchema, utoipa::IntoParams)] pub(super) struct WorkerDetailQuery { agent_id: String, worker_id: String, } -#[derive(Serialize)] +#[derive(Serialize, utoipa::ToSchema)] pub(super) struct WorkerDetailResponse { id: String, task: String, @@ -90,6 +90,22 @@ pub(super) struct WorkerDetailResponse { } /// List worker runs for an agent, with live status merged from StatusBlocks. +#[utoipa::path( + get, + path = "/agents/workers", + params( + ("agent_id" = String, Query, description = "Agent ID"), + ("limit" = i64, Query, description = "Maximum number of results to return"), + ("offset" = i64, Query, description = "Number of results to skip"), + ("status" = Option, Query, description = "Filter by worker status"), + ), + responses( + (status = 200, body = WorkerListResponse), + (status = 404, description = "Agent not found"), + (status = 500, description = "Internal server error"), + ), + tag = "workers", +)] pub(super) async fn list_workers( State(state): State>, Query(query): Query, @@ -165,6 +181,20 @@ pub(super) async fn list_workers( } /// Get full detail for a single worker run, including decompressed transcript. +#[utoipa::path( + get, + path = "/agents/workers/detail", + params( + ("agent_id" = String, Query, description = "Agent ID"), + ("worker_id" = String, Query, description = "Worker ID"), + ), + responses( + (status = 200, body = WorkerDetailResponse), + (status = 404, description = "Agent or worker not found"), + (status = 500, description = "Internal server error"), + ), + tag = "workers", +)] pub(super) async fn worker_detail( State(state): State>, Query(query): Query, diff --git a/src/bin/openapi_spec.rs b/src/bin/openapi_spec.rs new file mode 100644 index 000000000..71c2088e0 --- /dev/null +++ b/src/bin/openapi_spec.rs @@ -0,0 +1,9 @@ +fn main() { + match spacebot::openapi_json() { + Ok(json) => print!("{}", json), + Err(e) => { + eprintln!("Error generating OpenAPI spec: {}", e); + std::process::exit(1); + } + } +} diff --git a/src/config/types.rs b/src/config/types.rs index 77c949d31..d2284e764 100644 --- a/src/config/types.rs +++ b/src/config/types.rs @@ -785,7 +785,7 @@ impl Default for IngestionConfig { } /// What happens when a worker explicitly calls "close" on the browser. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize, utoipa::ToSchema)] #[serde(rename_all = "snake_case")] pub enum ClosePolicy { /// Kill the browser process and reset all state (current default behavior). @@ -1059,7 +1059,7 @@ impl Default for ProjectsConfig { } /// Current warmup lifecycle state. -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, utoipa::ToSchema)] #[serde(rename_all = "snake_case")] pub enum WarmupState { Cold, @@ -1069,7 +1069,7 @@ pub enum WarmupState { } /// Warmup runtime status snapshot for API and observability. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)] pub struct WarmupStatus { pub state: WarmupState, pub embedding_ready: bool, diff --git a/src/conversation/history.rs b/src/conversation/history.rs index 225a958a3..cf42e188c 100644 --- a/src/conversation/history.rs +++ b/src/conversation/history.rs @@ -244,7 +244,7 @@ impl ConversationLogger { } /// A unified timeline item combining messages, branch runs, and worker runs. -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, Serialize, utoipa::ToSchema)] #[serde(tag = "type", rename_all = "snake_case")] pub enum TimelineItem { Message { diff --git a/src/conversation/worker_transcript.rs b/src/conversation/worker_transcript.rs index 23ec340f2..7a21669ac 100644 --- a/src/conversation/worker_transcript.rs +++ b/src/conversation/worker_transcript.rs @@ -15,7 +15,7 @@ use std::io::{Read, Write}; const MAX_TOOL_ARGS_BYTES: usize = 2_000; /// A single step in a worker transcript. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)] #[serde(tag = "type", rename_all = "snake_case")] pub enum TranscriptStep { /// Agent reasoning and/or tool calls. @@ -40,7 +40,7 @@ pub enum TranscriptStep { } /// Content within an action step. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)] #[serde(tag = "type", rename_all = "snake_case")] pub enum ActionContent { Text { diff --git a/src/cron/store.rs b/src/cron/store.rs index 03abe9e9f..436973407 100644 --- a/src/cron/store.rs +++ b/src/cron/store.rs @@ -315,7 +315,7 @@ impl CronStore { } /// Entry in the cron execution log. -#[derive(Debug, Clone, serde::Serialize)] +#[derive(Debug, Clone, serde::Serialize, utoipa::ToSchema)] pub struct CronExecutionEntry { pub id: String, pub executed_at: String, diff --git a/src/factory/presets.rs b/src/factory/presets.rs index ddb2ae522..110fc3ad5 100644 --- a/src/factory/presets.rs +++ b/src/factory/presets.rs @@ -14,7 +14,7 @@ use rust_embed::Embed; struct PresetAssets; /// Metadata for a preset archetype (returned in list responses). -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, utoipa::ToSchema)] pub struct PresetMeta { pub id: String, pub name: String, @@ -31,14 +31,14 @@ pub struct PresetMeta { /// Model routing is intentionally excluded — presets are provider-agnostic. /// The factory conversation handles model selection at creation time when the /// user's available providers are known. -#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize, utoipa::ToSchema)] pub struct PresetDefaults { pub max_concurrent_workers: Option, pub max_turns: Option, } /// A fully loaded preset with all identity file content. -#[derive(Debug, Clone, serde::Serialize)] +#[derive(Debug, Clone, serde::Serialize, utoipa::ToSchema)] pub struct Preset { pub meta: PresetMeta, pub soul: String, diff --git a/src/lib.rs b/src/lib.rs index 5a4bc02d8..d334c1842 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,6 +35,14 @@ pub mod update; pub use error::{Error, Result}; +/// Generate the OpenAPI JSON specification. +/// This function is called by the `openapi-spec` binary. +pub fn openapi_json() -> anyhow::Result { + let (_, api) = api::api_router().split_for_parts(); + api.to_json() + .map_err(|e| anyhow::anyhow!("Failed to serialize OpenAPI spec: {}", e)) +} + use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::sync::Arc; diff --git a/src/main.rs b/src/main.rs index 708ac838f..6421ad567 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1545,7 +1545,7 @@ fn configured_agent_infos(config: &spacebot::config::Config) -> Vec; -#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)] +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, utoipa::ToSchema)] #[serde(rename_all = "snake_case")] pub enum McpConnectionState { Connecting, @@ -25,7 +25,7 @@ pub enum McpConnectionState { Disconnected, } -#[derive(Debug, Clone, serde::Serialize)] +#[derive(Debug, Clone, serde::Serialize, utoipa::ToSchema)] pub struct McpServerStatus { pub name: String, pub enabled: bool, diff --git a/src/memory/store.rs b/src/memory/store.rs index 36b104a81..b2f537587 100644 --- a/src/memory/store.rs +++ b/src/memory/store.rs @@ -82,7 +82,7 @@ impl MemoryStore { .bind(memory.last_accessed_at) .bind(memory.access_count) .bind(&memory.source) - .bind(memory.channel_id.as_ref().map(|id| id.as_ref())) + .bind(memory.channel_id.as_deref()) .bind(memory.forgotten) .execute(&self.pool) .await @@ -159,7 +159,7 @@ impl MemoryStore { .bind(memory.last_accessed_at) .bind(memory.access_count) .bind(&memory.source) - .bind(memory.channel_id.as_ref().map(|id| id.as_ref())) + .bind(memory.channel_id.as_deref()) .bind(memory.forgotten) .bind(&memory.id) .execute(&self.pool) @@ -290,7 +290,7 @@ impl MemoryStore { .bind(updated_survivor.last_accessed_at) .bind(updated_survivor.access_count) .bind(&updated_survivor.source) - .bind(updated_survivor.channel_id.as_ref().map(|id| id.as_ref())) + .bind(updated_survivor.channel_id.as_deref()) .bind(updated_survivor.forgotten) .bind(&updated_survivor.id) .execute(&mut *transaction) @@ -694,7 +694,7 @@ fn row_to_memory(row: &sqlx::sqlite::SqliteRow) -> Memory { .unwrap_or_else(|_| chrono::Utc::now()), access_count: row.try_get("access_count").unwrap_or(0), source: row.try_get("source").ok(), - channel_id: channel_id.map(|id| Arc::from(id) as crate::ChannelId), + channel_id, forgotten: row.try_get::("forgotten").unwrap_or(false), } } diff --git a/src/memory/types.rs b/src/memory/types.rs index 5c6cf88e2..e6d213267 100644 --- a/src/memory/types.rs +++ b/src/memory/types.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use uuid::Uuid; /// Memory structure. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] pub struct Memory { pub id: String, pub content: String, @@ -15,7 +15,7 @@ pub struct Memory { pub last_accessed_at: chrono::DateTime, pub access_count: i64, pub source: Option, - pub channel_id: Option, + pub channel_id: Option, /// Soft-delete flag. Forgotten memories are excluded from search and recall /// but remain in the database. pub forgotten: bool, @@ -57,7 +57,7 @@ impl Memory { /// Set the channel ID. pub fn with_channel_id(mut self, channel_id: crate::ChannelId) -> Self { - self.channel_id = Some(channel_id); + self.channel_id = Some(channel_id.to_string()); self } @@ -89,7 +89,7 @@ impl MemoryType { } /// Memory types. -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, utoipa::ToSchema)] #[serde(rename_all = "snake_case")] pub enum MemoryType { /// Something that is true. @@ -140,7 +140,7 @@ impl std::fmt::Display for MemoryType { } /// Association between memories. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] pub struct Association { pub id: String, pub source_id: String, @@ -175,7 +175,7 @@ impl Association { } /// Relation types for memory associations. -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, utoipa::ToSchema)] #[serde(rename_all = "snake_case")] pub enum RelationType { /// General semantic connection. @@ -206,7 +206,7 @@ impl std::fmt::Display for RelationType { } /// Search result combining memory with relevance score. -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, Serialize, utoipa::ToSchema)] pub struct MemorySearchResult { pub memory: Memory, pub score: f32, diff --git a/src/opencode/types.rs b/src/opencode/types.rs index a41007c5f..edaf95ab1 100644 --- a/src/opencode/types.rs +++ b/src/opencode/types.rs @@ -543,7 +543,7 @@ impl OpenCodeEnvConfig { /// /// This is a typed Rust subset of OpenCode's Part — only the variants the /// frontend cares about are included. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)] #[serde(tag = "type", rename_all = "snake_case")] pub enum OpenCodePart { /// Assistant text output. @@ -578,7 +578,7 @@ impl OpenCodePart { } /// Tool execution state for the frontend. Flattened into the Tool variant. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)] #[serde(tag = "status", rename_all = "snake_case")] pub enum OpenCodeToolState { Pending, diff --git a/src/projects/store.rs b/src/projects/store.rs index 3098c7511..2749406da 100644 --- a/src/projects/store.rs +++ b/src/projects/store.rs @@ -9,7 +9,7 @@ use sqlx::{Row as _, SqlitePool}; // Enums -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, utoipa::ToSchema)] #[serde(rename_all = "snake_case")] pub enum ProjectStatus { Active, @@ -60,7 +60,7 @@ pub struct ProjectSettings { // Domain types -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)] pub struct Project { pub id: String, pub agent_id: String, @@ -83,7 +83,7 @@ impl Project { } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)] pub struct ProjectRepo { pub id: String, pub project_id: String, @@ -99,7 +99,7 @@ pub struct ProjectRepo { pub updated_at: String, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)] pub struct ProjectWorktree { pub id: String, pub project_id: String, @@ -114,7 +114,7 @@ pub struct ProjectWorktree { } /// Full project with nested repos and worktrees for API responses. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)] pub struct ProjectWithRelations { #[serde(flatten)] pub project: Project, @@ -123,7 +123,7 @@ pub struct ProjectWithRelations { } /// Worktree with the source repo name resolved. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)] pub struct ProjectWorktreeWithRepo { #[serde(flatten)] pub worktree: ProjectWorktree, diff --git a/src/secrets/store.rs b/src/secrets/store.rs index 0e9ca9218..0f34b5331 100644 --- a/src/secrets/store.rs +++ b/src/secrets/store.rs @@ -73,7 +73,7 @@ impl Display for DecryptedSecret { /// All secrets are readable by Rust code via `SecretsStore::get()` regardless /// of category. The category answers one question: should this value be injected /// as an env var into worker subprocesses? -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema)] #[serde(rename_all = "lowercase")] pub enum SecretCategory { /// Not exposed to subprocesses. Rust code reads them for internal use @@ -920,7 +920,7 @@ impl SecretsStore { } /// Portable backup format for all secrets in a store. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)] pub struct ExportData { /// Format version (currently 1). pub version: u32, @@ -932,7 +932,7 @@ pub struct ExportData { } /// A single secret in the export format. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)] pub struct ExportEntry { pub name: String, pub value: String, diff --git a/src/tasks/store.rs b/src/tasks/store.rs index b74823e83..db3b0d117 100644 --- a/src/tasks/store.rs +++ b/src/tasks/store.rs @@ -11,7 +11,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use sqlx::{Row as _, SqlitePool}; -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, utoipa::ToSchema)] #[serde(rename_all = "snake_case")] pub enum TaskStatus { PendingApproval, @@ -58,7 +58,7 @@ impl std::fmt::Display for TaskStatus { } } -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, utoipa::ToSchema)] #[serde(rename_all = "snake_case")] pub enum TaskPriority { Critical, @@ -101,13 +101,13 @@ impl std::fmt::Display for TaskPriority { } } -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, utoipa::ToSchema)] pub struct TaskSubtask { pub title: String, pub completed: bool, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)] pub struct Task { pub id: String, pub task_number: i64, diff --git a/src/tools/memory_save.rs b/src/tools/memory_save.rs index c4a2e09b4..8193d7f50 100644 --- a/src/tools/memory_save.rs +++ b/src/tools/memory_save.rs @@ -396,7 +396,10 @@ impl Tool for MemorySaveTool { let event = ProcessEvent::MemorySaved { agent_id: event_context.agent_id.clone(), memory_id: memory.id.clone(), - channel_id: memory.channel_id.clone(), + channel_id: memory + .channel_id + .as_ref() + .map(|s| std::sync::Arc::from(s.as_str())), memory_type: memory.memory_type, importance: memory.importance, content_summary: summarize_memory_content(&memory.content), diff --git a/src/update.rs b/src/update.rs index 8bddbe8b3..399a93b96 100644 --- a/src/update.rs +++ b/src/update.rs @@ -19,7 +19,7 @@ pub const CURRENT_VERSION: &str = env!("CARGO_PKG_VERSION"); const CHECK_INTERVAL: Duration = Duration::from_secs(3600); /// Deployment environment, detected from SPACEBOT_DEPLOYMENT env var. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, utoipa::ToSchema)] #[serde(rename_all = "snake_case")] pub enum Deployment { Docker, @@ -55,7 +55,7 @@ fn is_running_in_container() -> bool { } /// Result of an update check. -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, Serialize, utoipa::ToSchema)] pub struct UpdateStatus { pub current_version: String, pub latest_version: Option,